Арифметика нулевого указателя - PullRequest
0 голосов
/ 17 января 2019

Я заметил это предупреждение от Clang:

warning: performing pointer arithmetic on a null pointer
has undefined behavior [-Wnull-pointer-arithmetic]

В деталях, именно этот код вызывает это предупреждение:

uint8_t *end = ((uint8_t*)0) + sizeof(uint8_t) * count;

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

И что более важно, стандарт C явно запрещает арифметику нулевого указателя ?

Ответы [ 2 ]

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

Небольшое уточнение по этой теме.

Прежде всего, это неопределенное поведение в соответствии со стандартом C по причинам, указанным StoryTeller:

Если и операнд-указатель, и результат указывают на элементы одного и того же объекта массива или один после последнего элемента объекта массива, при оценке не должно быть переполнения; в противном случае поведение не определено.

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

Однако , выполнение арифметических операций с нулевыми указателями для извлечения смещений не является новым, реализация C макроса offsetof использует его:

#define offsetof(st, m) ((size_t)&(((st *)0)->m))

И выполнение арифметических операций с указателями также часто:

int *end = (int *)0 + array_size;

Эта строка практически совпадает с записью:

int *end = (int *)(sizeof(int) * array_size);

Я полагаю, что вычисление смещения определяется реализацией, поскольку компилятор «мог» разыменовывать такие указатели для получения фактического смещения памяти, что, конечно, очень маловероятно, но все же возможно.

Также обратите внимание, что это предупреждение для арифметики с нулевым указателем относится к Clang 6.0. GCC не запускает его даже при -fsanitize=undefined.

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

Стандарт С не позволяет этого.

6.5.6 Аддитивные операторы (акцент мой)

8 Когда выражение, имеющее целочисленный тип, добавляется к или вычитается из указателя, результат имеет тип указателя операнд. Если операнд-указатель указывает на элемент массива объект , и массив достаточно велик, результат указывает на элемент смещение от исходного элемента, так что разница нижние индексы полученных и исходных элементов массива равны целочисленное выражение. Другими словами, если выражение P указывает на i-й элемент объекта массива, выражения (P) + N (эквивалентно, N + (P)) и (P) -N (где N имеет значение n) указывают соответственно на i + n-й и i-ный элементы массива, если они существуют. Более того, если выражение P указывает на последний элемент массива объект, выражение (P) +1 указывает один за последним элементом объект массива, и если выражение Q указывает один за последним элементом объекта массива, выражение (Q) -1 указывает на последний элемент объект массива. Если и операнд-указатель, и результат указывают на элементы одного и того же объекта массива, или один за последним элементом объект массива, оценка не должна вызывать переполнение; иначе, поведение не определено . Если результат указывает один за другим элемент массива, он не должен использоваться в качестве операнда унарный * оператор, который оценивается.

Для целей вышеизложенного указатель на отдельный объект рассматривается как указывающий на массив из 1 элемента.

Теперь ((uint8_t*)0) не указывает на элемент объекта массива. Просто потому, что указатель с нулевым значением указателя не указывает на какой-либо объект . Что сказано по адресу:

6.3.2.3 Указатели

3 Если константа нулевого указателя преобразуется в тип указателя, результирующий указатель, называемый нулевым указателем, гарантированно сравнивается не соответствует указателю на какой-либо объект или функцию.

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

Не обманывайтесь тем фактом, что макрос offsetof возможно реализован подобным образом. Стандартная библиотека не связана с ограничениями, накладываемыми на пользовательские программы. Это может использовать более глубокие знания. Но выполнение этого в нашем коде не очень хорошо определено.

...