Использование +, чтобы проверить, все ли указатели равны NULL - PullRequest
0 голосов
/ 06 июня 2018

Синтаксически это имеет смысл (хотя это выглядит как какой-то другой язык, который мне не особо нравится), он может сэкономить много печатания и места для кода, но насколько он плох?

if(p1 + (unsigned)p2 + (unsigned)p3 == NULL)
{
    // all pointers are NULL, exit
}

Используя арифметику указателей с указателем rvalue, я не вижу, как это могло бы дать ложный результат (все выражение должно оцениваться как NULL, хотя не все указатели имеют значение NULL), но я точно не знаю, насколько зла это потенциальноскрывает, так что плохо это делать, необычный способ проверки, все ли указатели равны NULL?

Ответы [ 3 ]

0 голосов
/ 06 июня 2018

Что касается оригинальной версии вопроса, в которой опущены приведения ...

это может сэкономить много печатания и места для кода, но насколько это плохо?

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

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

Я не понимаю, как это может дать ложный результат.1014 *

Если компилятор действительно его принимает, результатом может быть что угодно.Это undefined .

так что плохо это делать, необычный способ проверить, все ли указатели равны NULL?

Да.


Относительно измененного вопроса, в котором все указатели, кроме одного, приводятся к целому числу:

Приведения не спасают код - остаются многочисленные проблемы.

  1. Если оставшийся указатель не указывает на допустимый объект или если сумма целых чисел отрицательна или больше, чем количество элементов в массиве, на которое указывает указатель, то результатДобавление указателя по-прежнему не определено (где указатель на скаляр рассматривается как указатель на одноэлементный массив).Конечно, целочисленная сумма не может быть отрицательной в этом конкретном случае, но это имеет минимальное преимущество.

  2. C не гарантирует, что приведение нулевого указателя к целому числу приводит к значению 0Обычно это делается, но язык не требует этого.

  3. C не гарантирует, что ненулевые указатели преобразуются в ненулевые целые числа, и с вашим конкретным кодом этонастоящий риск.Тип unsigned не обязательно достаточно велик, чтобы предоставить отдельное значение каждому отдельному указателю.

  4. Даже если все вышеперечисленное не было проблемой для некоторой конкретной реализации - то есть, если бы вы могли безопасно выполнять арифметику с указателем NULL, а указатели NULL надежно преобразовывались в целые числа как ноль, а указатели, отличные от NULL, надежно преобразовывались в ненулевые - тест мог бы все еще идти не так, потому что два ненулевых целых числа без знакаможет суммировать до нуля.Это происходит, когда арифметическая сумма двух равна UINT_MAX + 1.

0 голосов
/ 06 июня 2018

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

Во-первых, когда вы добавляете целое число к указателю, стандарт 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. В этом случае у меня был прямой доступ к разработчикам компилятора и я мог убедиться, что компилятор будет вести себя так, как нужно. Это не стандартный код С.)

0 голосов
/ 06 июня 2018

Использование любого вида арифметики указателей для указателей, не являющихся массивами, является неопределенным поведением в C.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...