Может ли 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]);