Подтвердить что ты не робот

Может ли указатель (адрес) быть отрицательным?

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

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

[Обновить]

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

4b9b3361

Ответ 1

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

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

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

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

Он гарантирует наличие единого уникального адреса в вашей программе.

Ответ 2

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

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

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

Ответ 3

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

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

    *p = yourValue;
    return SUCCESS;
}

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

Ответ 4

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

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

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

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

Ответ 5

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

Ответ 6

Какая разница между неудачей и унитаризацией. Если 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 been initialized */

Ответ 7

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

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

Ответ 8

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

Лично я считаю, что адреса "интуитивно" неподписанны. Поиск указателя, который сравнивается с менее чем нулевым указателем, кажется неправильным. Но ~0 и -1 для одного и того же целочисленного типа дают одно и то же значение. Если он интуитивно беззнаковый, ~0 может сделать более интуитивное значение специального случая - я использую его для неподписанных целых чисел без ошибок. Это не совсем так (ноль - это int по умолчанию, поэтому ~0 есть -1, пока вы его не нажмете), но выглядит иначе.

Указатели на 32-битных системах могут использовать все 32 бита BTW, хотя -1 или ~0 является крайне маловероятным указателем для реального распределения на практике. Существуют также правила, специфичные для платформы - например, в 32-разрядной Windows, процесс может иметь только адресное пространство 2 ГБ, и там есть много кода, который кодирует какой-то флаг в верхний бит указателя (например, для балансировки флаги в сбалансированных бинарных деревьях).

Ответ 9

Собственно, (по крайней мере, на x86) исключение NULL-указателя генерируется не только разыменованием указателя NULL, но и большим диапазоном адресов (например, сначала 65kb). Это помогает поймать такие ошибки, как

int* x = NULL;
x[10] = 1;

Итак, есть больше адресов, которые гарантируются для генерации исключения NULL-указателя при разыменовании. Теперь рассмотрим этот код (сделанный для AndreyT):

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define ERR_NOT_ENOUGH_MEM (int)NULL
#define ERR_NEGATIVE       (int)NULL + 1
#define ERR_NOT_DIGIT      (int)NULL + 2

char* fn(int i){
    if (i < 0)
        return (char*)ERR_NEGATIVE;
    if (i >= 10)
        return (char*)ERR_NOT_DIGIT;
    char* rez = (char*)malloc(strlen("Hello World ")+sizeof(char)*2);
    if (rez)
        sprintf(rez, "Hello World %d", i);
    return rez;
};

int main(){
    char* rez = fn(3);
    switch((int)rez){
        case ERR_NOT_ENOUGH_MEM:    printf("Not enough memory!\n"); break;
        case ERR_NEGATIVE:          printf("The parameter was negative\n"); break;
        case ERR_NOT_DIGIT:         printf("The parameter is not a digit\n"); break;
        default:                    printf("we received %s\n", rez);
    };
    return 0;
};

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

Ответ 10

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

Ответ 11

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

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

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

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

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

Смотрите также

Ответ 12

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

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

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

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

Ответ 13

Положительный или отрицательный не является значимым аспектом типа указателя. Они относятся к целому числу со знаком, включая 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".