Может ли sizeof (arr [0]) привести к undefined поведению?

Существует известная схема вычисления длины массива:

int arr[10]; 
size_t len = sizeof(arr) / sizeof(arr[0]); 
assert(len == 10); 

Этот шаблон применяется к статическим массивам и автоматическим массивам постоянного размера. Это также относится к массивам переменной длины в C99.

Я хочу применить аналогичную идею для вычисления размера динамического массива в байтах:

size_t known_len = 10; 
int *ptr = malloc(known_len * sizeof(int)); 
size_t size = known_len * sizeof(ptr[0]); 
assert(size == known_len * sizeof(int)); 

Это лучше, чем known_len * sizeof(int), потому что sizeof(ptr[0]) не относится к типу элемента массива. Следовательно, это не требует, чтобы считыватель кода знал тип.

Однако мне непонятно, может ли выражение sizeof(ptr[0]) привести к поведению undefined. Поскольку он расширен:

sizeof(ptr[0]) -> sizeof(*((ptr) + (0))) -> sizeof(*ptr) 

Выражение результата сомнительно, если ptr равно 0:

sizeof(*((int*) 0)) 

В соответствии со стандартом C99:

(C99, 6.3.2.3p3): "Целочисленное постоянное выражение со значением 0, или такое выражение, которое применяется к типу void *, называется нулевым указателем константа". Отмена нулевого указателя имеет поведение undefined.

(C99, 6.5.3.2.p4) "Если недопустимое значение было присвоено указатель, поведение унарного оператора * undefined.87)"

87): "Среди недопустимых значений для разыменования указателя Унарный оператор * - это нулевой указатель, неправильный адрес выровненный для типа объекта, на который указывает, и адрес объект после окончания его жизни."

Но он никогда не указывал, может ли sizeof такого выражения привести к поведению undefined. Фактически такой sizeof следует оценивать во время компиляции.

Мои вопросы:

  • Можно ли использовать выражение sizeof(ptr[0]) в коде, когда тип ptr известен, а значение ptr неизвестно?
  • Может ли такое использование быть оправданным в соответствии со стандартом C99? Спецификация GNU GCC?

Ответы

Ответ 1

Выражение ptr[0] не будет оцениваться в sizeof(ptr[0]). Размер будет определяться только с использованием типа ptr[0] во время компиляции.

C11: 6.5.3.4:

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

Это означает, что поведение undefined отсутствует.

Ответ 2

Это не приведет к поведению undefined.

За исключением размера массивов переменной длины, sizeof - это выражение постоянной времени компиляции. Компилятор обрабатывает выражение для определения его типа, не создавая кода для оценки выражения во время компиляции. Следовательно, значение в ptr[0] (которое является неинициализированным указателем) не имеет значения.

Кроме того, если вы хотите выделить десять целых чисел, вы должны называть malloc следующим образом:

int *ptr = malloc(known_len * sizeof(ptr[0])); 

В противном случае вы выделяете десять байтов, что слишком мало для хранения десяти целых чисел. Обратите внимание, что в выражении выше ptr неинициализируется во время вызова, что отлично подходит для sizeof.

Ответ 3

В общем случае, если я что-то упускаю, разыменование нулевого указателя в sizeof может привести к поведению undefined. Поскольку C99, sizeof не является чисто компиляцией времени. Операнд sizeof оценивается во время выполнения, если тип операнда является VLA.

Рассмотрим следующий пример

unsigned n = 10;
int (*a)[n] = NULL; // `a` is a pointer to a VLA 

unsigned i = 0;
sizeof a[i++];      // applying `sizeof` to a VLA

В соответствии со стандартом C99 аргумент sizeof должен быть оценен (т.е. i должен увеличиваться). Тем не менее я не совсем уверен, что разыменование нулевой точки в a[0] должно приводить к возникновению поведения undefined здесь.

Ответ 4

Как выражение (не объявление), ptr[0] идентично *ptr.

Пока тип ptr известен и не указывает на void, sizeof гарантированно работает на sizeof(*ptr), а также sizeof(ptr[0]).

Ответ 5

Код злой.

size_t known_len = 10; 
int *ptr = malloc(known_len); 
size_t size = known_len * sizeof(ptr[0]); 
assert(size == known_len * sizeof(int)); 

Размер выделения памяти из вашей точки обзора составляет 10 байтов. Это не 10 указателей на int.

 known_len / sizeof(ptr[0]) 

предоставит вам количество целых чисел, которые может содержать выделение.

Как и вы, вы, вероятно, уйдете с конца распределения, что приведет к поведению undefined.

Рассмотрим malloc (known_length * sizeof ptr [0]);