Может ли указатель (адрес) быть отрицательным? - PullRequest
33 голосов
/ 22 июля 2010

У меня есть функция, которую я хотел бы иметь возможность возвращать специальные значения для ошибка и неинициализирован (возвращает указатель на успех).

В настоящее времяон возвращает NULL для сбоя и -1 для неинициализированного, и это, кажется, работает ... но я мог бы обмануть систему.IIRC, адреса всегда положительные, не так ли?(хотя компилятор позволяет мне установить адрес -1, это кажется странным).

[update]

Еще одна идея, которая у меня возникла (если -1 был рискованным)malloc символ @ глобальная область и использовать этот адрес в качестве дозорного.

Ответы [ 13 ]

70 голосов
/ 22 июля 2010

Нет, адреса не всегда положительные - на x86_64 указатели расширены по знаку, а адресное пространство сгруппировано симметрично вокруг 0 ​​(хотя обычно «отрицательные» адреса являются адресами ядра).

Тем не менее, точка в основном спорная, поскольку C определяет только значения сравнения указателей < и > между указателями, которые являются частью одного и того же объекта, или одним после конца массива. Указатели на совершенно разные объекты не могут быть осмысленно сопоставлены, кроме как для точного равенства, по крайней мере в стандарте C - if (p < NULL) не имеет четко определенной семантики.

Вы должны создать фиктивный объект со статической продолжительностью хранения и использовать его адрес в качестве значения unintialised:

extern char uninit_sentinel;
#define UNINITIALISED ((void *)&uninit_sentinel)

Гарантируется, что в вашей программе будет один уникальный адрес.

20 голосов
/ 22 июля 2010

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

Более важно, однако, рассмотреть (в качестве примера возможного выбора реализации) случай, когда вы находитесь на 32-битной платформе с 32-битным размером указателя. Любое значение, которое может быть представлено этим 32-битным значением, может быть допустимым указателем. Кроме нулевого указателя, любое значение указателя может быть допустимым указателем на объект.

Для вашего конкретного случая использования вы должны рассмотреть возможность возврата кода состояния и, возможно, использования указателя в качестве параметра функции.

17 голосов
/ 22 июля 2010

Обычно плохой дизайн - пытаться мультиплексировать специальные значения в возвращаемое значение ... вы пытаетесь сделать слишком много с одним значением.Было бы чище возвращать ваш «указатель успеха» через аргумент, а не возвращаемое значение.Это оставляет много не конфликтующих пробелов в возвращаемом значении для всех условий, которые вы хотите описать:

int SomeFunction(SomeType **p)
{
    *p = NULL;
    if (/* check for uninitialized ... */)
        return UNINITIALIZED;
    if (/* check for failure ... */)
        return FAILURE;

    *p = yourValue;
    return SUCCESS;
}

Вы также должны выполнить типичную проверку аргументов (убедитесь, что 'p' не равно NULL).

5 голосов
/ 22 июля 2010

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

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

Если вы принудительно установите значение -1 к типу указателя с помощью явного приведения, результат приведения будет определяться реализацией.Сам язык не дает никаких гарантий по этому поводу.Это может легко оказаться тем же, что и какое-либо другое действительное значение указателя.

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

4 голосов
/ 22 июля 2010

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

1 голос
/ 22 июля 2010

В чем разница между неудачей и унифицированным.Если unitialized не является другим видом сбоя, то вы, вероятно, захотите изменить интерфейс для разделения этих двух условий.

Вероятно, лучший способ сделать это - вернуть результат через параметр, поэтому возвращаемое значениеуказывает на ошибку.Например, где вы написали бы:

void* func();

void* result=func();
if (result==0)
  /* handle error */
else if (result==-1)
  /* unitialized */
else
  /* initialized */

Измените это на

// sets the *a to the returned object
// *a will be null if the object has not been initialized
// returns true on success, false otherwise
int func(void** a);

void* result;
if (func(&result)){
  /* handle error */
  return;
}

/*do real stuff now*/
if (!result){
  /* initialize */
}
/* continue using the result now that it's been initialized */
0 голосов
/ 22 мая 2019

Вам не нужно заботиться о значимости указателя, потому что его реализация определена.Настоящий вопрос здесь «как вернуть специальные значения из функции, возвращающей указатель?» , которую я подробно объяснил в своем ответе на вопрос Диапазон адресов указателей на различных платформах

Таким образом, битовый шаблон «все-один» (-1) почти наверняка безопасен, потому что он уже находится в конце спектра, и данные не могут быть сохранены, обернутые вокруг первого адреса.Он даже возвращается многими системными вызовами Linux, чтобы указать другое состояние для указателя.Так что если вам нужны только ошибка и неинициализированная , тогда это хороший выбор

Но вы можете вернуть гораздо больше состояний ошибки, используя тот факт, что переменные должны быть правильно выровненыесли вы не указали некоторые другие параметры).Например, в указателе на int32_t младшие 2 бита всегда равны нулю, что означает, что только ¹⁄₄ возможных значений являются действительными адресами, оставляя все оставшиеся битовые комбинации для использования.Поэтому простым решением будет просто проверить младший бит

int* result = func();
if ((uintptr_t)result & 1)
    uninitialized();

Вы также можете использовать старшие биты для хранения данных в 64-битных системах.На ARM есть флаг, который указывает процессору игнорировать старшие биты в адресах.В x86 такого нет, но вы можете использовать эти биты, если сделаете их каноническими перед разыменованием.См. Использование дополнительных 16 битов в 64-разрядных указателях

См. Также

0 голосов
/ 17 июня 2014

Положительный или отрицательный не является значимым аспектом типа указателя.Они относятся к целому числу со знаком, в том числе знаковому типу char, short, int и т. Д.

Люди, которые говорят об отрицательном указателе, в основном в ситуации, когда машинное представление указателя рассматривается как целочисленный тип.например, reinterpret_cast<intptr_t>(ptr).В этом случае они на самом деле говорят о приведенном целом числе.не сам указатель.

В некотором сценарии я думаю, что указатель по своей сути не подписан, мы говорим об адресе в терминах ниже или выше.0xFFFF.FFFF выше 0x0AAAA.0000, что интуитивно понятно для людей.хотя 0xFFFF.FFFF на самом деле является «отрицательным», в то время как 0x0AAA.0000 является положительным.

Но в других сценариях вычитание указателя (ptr1 - ptr2) приводит к знаковому значению, тип которого ptrdiff_t, оно несовместимо при сравнении свычитание целого числа, signed_int_a - signed_int_b приводит к типу int со знаком, unsigned_int_a - unsigned_int_b создает тип без знака.Но для вычитания указателя он создает тип со знаком, потому что семантика - это расстояние между двумя указателями, единица измерения - количество элементов.

В заключение я предлагаю рассматривать тип указателя как автономный тип, каждый тип имеет свой набороперации на нем.Для указателей (исключая указатель на функцию, указатель на функцию-член и void *):

  1. Элемент списка
  2. +, + =

    ptr +any_integer_type

  3. -, - =

    ptr - any_integer_type

    ptr1 - ptr2

  4. ++ префикс и постфикс

  5. - префикс и постфикс

Обратите внимание, что для указателя нет операции / * %.Также поддерживается то, что указатель должен обрабатываться как отдельный тип, а не «тип, похожий на int» или «тип, базовый тип которого int, поэтому он должен выглядеть как int».

0 голосов
/ 07 августа 2010

Не используйте malloc для этой цели. Это может держать ненужную память связанной (если много памяти уже используется, когда вызывается malloc, и, например, стражу выделяется высокий адрес), и это сбивает с толку отладчики памяти / детекторы утечки. Вместо этого просто верните указатель на локальный объект static const char. Этот указатель никогда не будет сравниваться с указателем, который может получить программа любым другим способом, и он тратит только один байт bss.

0 голосов
/ 22 июля 2010

NULL - единственное допустимое возвращение ошибки в этом случае, это верно каждый раз, когда возвращается значение без знака, такое как указатель.Может быть и так, что в некоторых случаях указатели не будут достаточно большими, чтобы использовать знаковый бит в качестве бита данных, однако, поскольку указатели управляются ОС, а не программой, я бы не стал полагаться на это поведение.

Помнитечто указатель является в основном 32-битным значением;является ли это возможным отрицательным или всегда положительным числом, это просто вопрос интерпретации (т. е.) интерпретируется ли бит 32 nd как знаковый бит или как бит данных.Поэтому если вы интерпретируете 0xFFFFFFF как число со знаком, это будет -1, если вы интерпретируете его как число без знака, это будет 4294967295. Технически маловероятно, что указатель когда-либо будет таким большим, но этот случай следует рассмотреть в любом случае.

В качестве альтернативы вы можете использовать дополнительный параметр out (возвращающий NULL для всех сбоев), однако это потребует от клиентов создания и передачи значения дажеесли им не нужно различать конкретные ошибки.

Другой альтернативой может быть использование механизма GetLastError / SetLastError для предоставления дополнительной информации об ошибках (Это относится к Windows, не знаюесли это проблема или нет), или вместо этого вывести исключение при ошибке.

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