Выражение размера параметра массива переменной длины с побочными эффектами

Этот вопрос возник из-за замечания Эрика Постпишила, сделанного в другом потоке.

Мне сложно понять использование массивов переменной длины (VLA) в качестве параметров функции:

  • Размер массива не проверяется.
  • Размер массива не может быть восстановлен из массива, потому что стандартный тип настройки array -> указатель также применяется для VLA, как демонстрируют вызовы sizeof() ниже; даже если было бы вполне возможно передать весь массив в стек, точно так же, как VLA создаются в стеке при их определении.
  • Размер необходимо передавать как дополнительный параметр, как и в случае указателей.

Так почему же язык позволяет объявлять функцию с параметрами VLA, если они не дают никаких преимуществ и настраиваются, как любой другой аргумент массива для указателя? Почему выражение размера оценивается, если оно не используется языком (например, для проверки размера фактического аргумента) и не может быть получено внутри функции (для этого все равно необходимо передать явную переменную) ??

Чтобы прояснить, что меня сбивает с толку, рассмотрите следующую программу (живой пример здесь). Все объявления функций явно эквивалентны. Но, как указал Эрик в другом потоке, выражение размера параметра в объявлении функции оценивается во время выполнения. Выражение размера не игнорируется.

Мне неясно, какая польза от этого, потому что размер и его оценка не имеют никакого эффекта (помимо возможных побочных эффектов). В частности, повторюсь, эта информация не может использоваться кодом внутри функции. Наиболее очевидным изменением было бы передать VLA в стеке подобных структур. В конце концов, они обычно также находятся в стеке на стороне вызывающего. Но, как и в случае с массивами постоянной длины, тип настраивается уже во время объявления на указатель, все объявления ниже эквивалентны. Тем не менее вычисляется бесполезное и отброшенное выражение размера массива.

#include <stdio.h>

// Nothing to see here.
extern void ptr(int *arr);

// Identical to the above.
extern void ptr(int arr[]);

// Still identical. Is 1 evaluated? Who knows ;-).
extern void ptr(int arr[1]);

// Is printf evaluated when called? Yes.
// But the array is still adjusted to a pointer.
void ptr(int arr[printf("Call-time evaluation of size parameter\n")]){}

// This would not compile, so the declarations above must be equivalent.
// extern void ptr(int **p);

int main()
{
    ptr(0);
    ptr(0);

    return 0;
}

person Peter - Reinstate Monica    schedule 26.01.2019    source источник
comment
@ChronoKitsune: Этот вопрос в первую очередь не о распаде массива. Фактически, это вообще не связано с распадом массива; С другой стороны, конкретная проблема связана с настройкой типа параметра. Но не это главное.   -  person Eric Postpischil    schedule 26.01.2019
comment
@ChronoKitsune Вопрос включает распад массива (или, скорее, настройку типа параметра), но это не основная тема вопроса. Вопрос касается времени оценки аргументов размера. Мне также было интересно, почему разрешено использование VLA в качестве параметров функции, если они рассматриваются как обычные массивы. Вы знаете, они могут передаваться по значению, как структуры; с массивами фиксированного размера, которые имели бы лишь ограниченный смысл, как указано в разделе вопросов и ответов, который я опубликовал недавно..   -  person Peter - Reinstate Monica    schedule 27.01.2019
comment
@ChronoKitsune Итак, почему размер параметра VLA оценивается, когда он вообще не используется, и даже не является размером фактического аргумента ??   -  person Peter - Reinstate Monica    schedule 27.01.2019
comment
@JeanFrancois Это не дубликат известного вопроса, на который вы указали.   -  person Peter - Reinstate Monica    schedule 27.01.2019
comment
@JeanFrancois: Этот вопрос не о том, когда и почему массивы преобразуются в указатели или когда типы параметров массива корректируются в типы указателей. Этот вопрос касается вычисления выражений размера в параметрах массива. Пожалуйста, не помечайте вопросы как дубликаты ненадлежащим образом.   -  person Eric Postpischil    schedule 27.01.2019
comment
@ PeterA.Schneider Моя ошибка. Вопрос не следует закрывать дубликатом, как я изначально думал.   -  person    schedule 27.01.2019


Ответы (3)


... можно опустить переменную размера и ... размер, который будет оцениваться во время выполнения, ...; но какая польза?

Информация о размере верхнего уровня потеряна ????, в аргументе массива теперь является параметр указателя. Однако с аргументами функции 2D VLA, которые превращаются в указатель на массив 1D, код знает об этом измерении массива.

void g(size_t size, size_t size2, int arr[size][size2]) {
  printf("g: %zu\n", sizeof(arr));
  printf("g: %zu\n", sizeof(arr[0]));
}

int main(void) {
  int arr[10][7];
  g(10, 7, arr);
}

Выход

g: 8   pointer size
g: 28  7 * int size

В качестве альтернативы можно передать указатель на массив.

void g2(size_t size, size_t size2, int (*arr)[size][size2]) {
  printf("g2: %zu\n", sizeof(arr));
  printf("g2: %zu\n", sizeof(*arr));
}

int main(void) {
  int arr[10][7];
  g2(10, 7, &arr);
}

Выход

g2: 8    pointer size
g2: 280  10 * 7 * int size
person chux - Reinstate Monica    schedule 26.01.2019
comment
Я знаю синтаксис и семантику 2D-массивов и нерелевантность крайнего левого размера в параметре массива (потому что на самом деле это объявление указателя). Мой вопрос был скорее в том, почему VLA не обрабатываются умнее, например передается по значению как структуры. - person Peter - Reinstate Monica; 27.01.2019
comment
@ PeterA.Schneider Честно говоря, в вашем отредактированном посте теперь есть мой вопрос, скорее, почему VLA ... через несколько часов после 2 ответов. Лучше всего добавить четкие вопросы в начальный пост. Я нашел только тот, который перефразирован в этом ответе. - person chux - Reinstate Monica; 27.01.2019
comment
Извините за позднее редактирование. Я выразил недоумение только в первой версии вопроса и добавил явные вопросы, отражающие недоумение, только после того, как понял, что было непонятно, о чем я спрашивал, во всяком случае (может быть, это больше, чем что-либо, выдыхание и поиск мысленного удержания). Вопрос был вызван осознанием того, что в современном C выражение размера в объявлении функции вообще оценивается (в то время как в C89 оно игнорируется, потому что это даже не объявление массива, а информация теряется, как вы правильно объяснили). - person Peter - Reinstate Monica; 27.01.2019
comment
Хороший момент в том, что у вас может быть универсальная функция, принимающая указатель на 2D-массив, второе измерение которого также является переменным. Это действительно невозможно с массивами фиксированного размера (для более сложных и обычно требующих динамического распределения может потребоваться массив указателей). - person Peter - Reinstate Monica; 27.01.2019

C 2018 6.9.1 обсуждает определения функций и сообщает нам в параграфе 10:

При входе в функцию вычисляются выражения размера каждого изменяемого параметра…

Согласно 6.7.6 3, изменяемый тип - это тип, который имеет тип массива переменной длины в своих деклараторах, возможно, вложенный. (Таким образом, int a[n] изменяется по-разному, поскольку это массив переменной длины, а int (*a[3])[n] фиксированной длины также изменяется по-разному, поскольку вложенный в него тип массива переменной длины.)

В случае void foo(int n, int a[][n]) мы видим, что n необходимо вычислить, потому что компилятору нужен размер для вычисления адресов для таких выражений, как a[i][j]. Однако для void foo(int n, int a[n]) такой необходимости нет, и мне не ясно, применяется ли приведенный выше текст к типу параметра до настройки (int a[n]) или после настройки (int *a).

Насколько я помню, когда я впервые обратил на это внимание несколько лет назад, я нашел как компилятор, который оценивал выражение, так и компилятор, который этого не делал, для прямого параметра массива. Вызов foo, который был определен с помощью void foo(int a[printf("Hello, world.\n")]) {}, выводит или не выводит строку в зависимости от компилятора. В настоящее время при компиляции с Apple LLVM 10.0.0 и clang-1000.11.45.5 в macOS 10.14.2 строка печатается. (Как упоминалось выше, для типа вложенного массива выражение должно быть вычислено, и все компиляторы, которые я пробовал, показали это. К сожалению, в настоящее время я не помню, какие это были компиляторы.)

Не ясно, что размер массива полезен для компилятора. Этот аспект стандарта C мог быть не полностью проработан. Есть особенность, которая добавляет некоторого смысла размеру; если размер объявлен с static:

void foo(int a[static SomeExpression]) { … }

тогда, согласно 6.7.6.3 7, a должен указывать как минимум на SomeExpression элементов. Это означает, что a не должен быть нулевым, что компилятор может использовать для оптимизации некоторых вещей. Однако у меня нет примеров того, как само число может помочь в оптимизации или других аспектах компиляции.

person Eric Postpischil    schedule 26.01.2019
comment
Вопрос, конечно же, был результатом вашего замечания в другой ветке. Замечательная функция C, неизвестная мне до сегодняшнего дня. Спасибо за ответы. - person Peter - Reinstate Monica; 27.01.2019
comment
@ PeterA.Schneider: int (puts) (); int main(p, q) int p; char *q [(puts) (&*"Hello, world.")]; {} - это полная программа на C, которая компилируется в Clang без предупреждений даже с -std=c11 -pedantic -Wall. Никогда не используйте это. :-) - person Eric Postpischil; 27.01.2019

Я не вижу практического использования VLA в качестве параметра функции.

Только указатели на массивы имеют смысл, так как упрощают обход массивов и дают правильную информацию о размере.

int foo(int (*p)[2])
{

    printf("sizeof int = %zu\n", sizeof(int));
    printf("p + 0:%p\n", (void *)p);
    printf("p + 1:%p\n", (void *)(p + 1));
}

void g(size_t size, size_t size2, int (*arr)[size][size2]) 
{
  printf("g: %zu\n", sizeof(*arr));
  printf("g: %zu\n", sizeof(*arr[0]));
}


int main()
{
    foo(0);
    g(5,5,0);

    return 0;
}

sizeof int = 4                                                                                                                                                                                                                                              
p + 0:(nil)                                                                                                                                                                                                                                                 
p + 1:0x8                                                                                                                                                                                                                                                   
g: 100                                                                                                                                                                                                                                                      
g: 20     
person 0___________    schedule 26.01.2019