Использование аргументов массива в C считается плохой практикой? - PullRequest
0 голосов
/ 14 января 2019

При объявлении функции, которая обращается к нескольким последовательным значениям в памяти, я обычно использую аргументы массива, такие как

f(int a[4]);

Это прекрасно работает для моих целей. Однако недавно я прочитал мнение Линуса Торвальдса .

Так мне интересно, считаются ли аргументы массива устаревшими? В частности,

  • есть ли случай, когда компилятор может использовать эту информацию (размер массива) для проверки внешнего доступа, или
  • Есть ли случаи, когда этот метод дает некоторые возможности оптимизации?

В любом случае, как насчет указателей на массивы?

void f(int (*a)[4]);

Обратите внимание, что эта форма не подвержена ошибкам "sizeof". Но как насчет эффективности в этом случае? Я знаю, что GCC генерирует тот же код ( ссылка ). Это всегда так? А как насчет дальнейших возможностей оптимизации в этом случае?

Ответы [ 2 ]

0 голосов
/ 14 января 2019

Если это не операнд sizeof или унарные операторы &, или строковый литерал, используемый для инициализации массива символов в объявлении, выражение типа "массив N-элементов из T "будет преобразовано (" распад ") в выражение типа" указатель на T ", а значением выражения будет адрес первого элемента в массиве.

Когда вы передаете выражение массива в качестве аргумента функции:

int arr[100];
...
foo( arr );

то, что на самом деле получает функция, это указатель на первый элемент массива, а не его копия. Поведение точно такое же, как если бы вы написали

foo( &arr[0] );

Существует правило, что параметры функции типа T a[N] или T a[] "настраиваются" на T *a, поэтому, если ваше объявление функции

void foo( int a[100] )

будет интерпретироваться как если бы вы написали

void foo( int *a )

Есть несколько существенных последствий этого:

  • Массивы неявно передаются «по ссылке» функциям, поэтому изменения содержимого массива в функции отражаются в вызывающей программе (в отличие от буквально любого другого типа );

  • Вы не можете использовать sizeof для определения количества элементов в переданном массиве, потому что нет способа получить эту информацию из указателя. Если вашей функции необходимо знать физический размер массива, чтобы правильно его использовать, вы должны передать эту длину в качестве отдельного параметра 1 .

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

Обратите внимание, что у вас та же проблема с указателями на массивы - если я позвоню

foo( &arr );

тогда прототип для foo должен быть

void foo( int (*a)[100] );

Но это также тот же прототип, как если бы я назвал его

void bar[10][100];

foo( bar );  

Так же, как вы не можете знать, указывает ли параметр a на единственный int или первый в последовательности int с, вы не можете знать, указывает ли bar на один массив из 100 элементов или первому в последовательности массивов из 100 элементов.


Именно поэтому функция gets устарела после C99 и удалена из стандартной библиотеки в C2011 - невозможно определить размер целевого буфера, поэтому она с радостью записывает ввод после конца массива и раздолбай все, что следует. Вот почему это был такой популярный вредоносный эксплойт.
0 голосов
/ 14 января 2019

Если вы напишите

void f(int a[4]);

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

void f(int *a);

Вот почему у Линуса такое мнение. [4] выглядит как , оно определяет ожидаемый размер массива, но это не так. Несоответствие между тем, как выглядит код, и тем, что он на самом деле означает, очень плохо, когда вы пытаетесь поддерживать большую и сложную программу.

(В общем, я советую людям , а не предполагать, что мнения Линуса верны. В этом случае я согласен с ним, но я бы не сказал это так злобно.)

Начиная с C99, существует вариант, который означает , что означает, что он выглядит так:

void f(int a[static 4]);

То есть все вызывающие f обязаны указывать на массив не менее четырех int с; если они этого не делают, программа имеет неопределенное поведение. Это может помочь оптимизатору, по крайней мере, в принципе (например, это может означать, что цикл над a[i] внутри f может быть векторизован).

Ваш альтернативный конструкт

void f(int (*a)[4]);

дает параметру a другой тип («указатель на массив 4 int» вместо «указатель на int»). Эквивалент обозначений массива этого типа

void f(int a[][4]);

Таким образом, должно быть ясно, что это объявление подходит, когда аргументом f является двумерный массив , внутренний размер которого равен 4, но не иначе.

sizeof проблемы - еще одна банка червей; Я рекомендую избегать необходимости использовать sizeof для аргументов функции практически любой ценой. не искажайте список параметров функции, чтобы sizeof выходил "прямо" из функции; это затрудняет правильный вызов функции, и вы, вероятно, вызываете функцию намного чаще, чем реализуете ее.

...