Существует несколько причин, по которым этот метод не является надежным.
Во-первых, когда вы добавляете целое число к указателю, стандарт C не говорит, что происходит, если результат находится вне массива, в которыйуказатель указывает.(В этих целях указание только на один последний элемент, конец массива считается как внутри, а не снаружи. Кроме того, указатель на один объект считается как массив одного объекта.) Обратите внимание, что стандарт C непросто не говорите, каков результат сложения;в нем не говорится, каково поведение всей программы .Поэтому, когда вы выполняете сложение, выходящее за пределы массива, вы не можете предсказать (из стандарта C), что ваша программа вообще сделает.
Одним из вероятных результатов является то, что компилятор увидит pointer + integer + integer
иобосновать (или, более технически, применить преобразования, как если бы использовались эти рассуждения), что pointer + integer
действителен, только если pointer
не NULL
, а затем результат никогда не будет NULL
, поэтому выражение pointer + integer
никогда NULL
.Точно так же, pointer + integer + integer
никогда не будет NULL
.Поэтому pointer + integer + integer == NULL
всегда ложно, и мы можем оптимизировать программу, полностью удалив этот код.Таким образом, код, обрабатывающий случай, когда все указатели NULL
, будет тихо удален из вашей программы.
Во-вторых, даже если стандарт C действительно гарантирует результат добавления, это выражение может, гипотетически,оцените NULL
, даже если ни один из указателей не был NULL
.Например, рассмотрим 16-разрядное адресное пространство, в котором первый указатель был представлен адресом 0x7000, второй - 0x6000, а третий - 0x3000.(Я также предполагаю, что это char *
указатели, поэтому один элемент равен одному байту.) Если мы добавим их, математический результат будет 0x10000.В 16-битной арифметике это оборачивается, поэтому вычисленный результат равен 0x0000.Таким образом, выражение может быть равно нулю, что, вероятно, используется для NULL
.
В-третьих, unsigned
может быть уже, чем указатели (например, это может быть 32 бита, в то время как указатели 64), поэтомуприведение может потерять информацию - в битах, которые были потеряны во время преобразования, могут быть ненулевые биты, поэтому тест не сможет их обнаружить.
В некоторых ситуациях мы хотим оптимизировать тесты указателей, иЕсть законные, но нестандартные способы сделать это.На некоторых процессорах ветвление может быть дорогим, поэтому выполнение некоторой арифметики с одним тестом и одной ветвью может быть быстрее, чем выполнение трех тестов и трех ветвей.C предоставляет целочисленный тип, предназначенный для работы с представлениями указателей: uintptr_t
, объявленный в <stdint.h>
.С этим мы можем написать этот код:
if (((uintptr_t) p1 | (uintptr_t) p2 | (uintptr_t) p3) == 0) …
Что это делает, это конвертировать каждый указатель в целое число без знака ширины, подходящее для работы с представлениями указателя.Стандарт C не говорит о том, каков результат этого преобразования, но предполагается, что это неудивительно, и реализации C для плоских адресных пространств могут документировать, что результатом является адрес памяти.Они также могут задокументировать, что NULL
является нулевым адресом.Когда у нас есть эти целые числа, мы ИЛИ их вместе вместо того, чтобы добавлять их.Результат ИЛИ имеет установленный бит, если был установлен любой из соответствующих битов в его операндах.Таким образом, если любой из адресов не равен нулю, результат также не будет равен нулю.Таким образом, этот код, если он выполняется в подходящей реализации на языке C, будет выполнять желаемый вами тест.
(я использовал такие тесты в специальном высокопроизводительном коде для проверки того, все ли указатели были выровнены по желанию, а не длятест для NULL
. В этом случае у меня был прямой доступ к разработчикам компилятора и я мог убедиться, что компилятор будет вести себя так, как нужно. Это не стандартный код С.)