Выражение размера параметра массива переменной длины с побочными эффектами - PullRequest
0 голосов
/ 26 января 2019

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

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

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

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

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

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

#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;
}

Ответы [ 3 ]

0 голосов
/ 26 января 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 не должно быть нулевым, что компилятор может использовать для оптимизации некоторых вещей.Однако у меня нет примеров того, как само число может помочь с оптимизацией или другими аспектами компиляции.

0 голосов
/ 27 января 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     
0 голосов
/ 26 января 2019

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

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

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
...