指针(地址)可以是负数吗?

时间:2010-07-21 23:55:55

标签: c pointers return-value signed

我有一个函数,我希望能够为失败未初始化返回特殊值(它会在成功时返回指针)。

目前它返回NULL表示失败,而-1表示未初始化,这似乎有效......但我可能会欺骗系统。 IIRC,地址总是积极的,不是吗? (虽然编译器允许我将地址设置为-1,但这看起来很奇怪。)

[更新]

我有另一个想法(如果-1有风险)是malloc一个char @全局范围,并将该地址用作哨兵。

13 个答案:

答案 0 :(得分:70)

不,地址并不总是正数 - 在x86_64上,指针是符号扩展的,地址空间对称地聚集在0左右(尽管通常“负”地址是内核地址)。

然而,这一点大多没有实际意义,因为C只定义了指向同一个对象的一个​​指针之间的<>指针比较的含义,或一个超出数组末尾的指针。指向完全不同的对象的指针除了完全相等之外不能进行有意义的比较,至少在标准C中 - if (p < NULL)没有明确定义的语义。

您应该创建一个具有静态存储持续时间的虚拟对象,并将其地址用作unintialised值:

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

保证您的计划中有一个唯一的地址。

答案 1 :(得分:20)

指针的有效值完全取决于实现,因此,是的,指针地址可能为负。

然而,更重要的是,考虑(作为可能的实现选择的示例)您使用32位指针大小的32位平台的情况。可以由该32位值表示的任何值都可以是有效指针。除空指针外,任何指针值都可能是指向对象的有效指针。

对于您的特定用例,您应该考虑返回状态代码,并可能将指针作为函数的参数。

答案 2 :(得分:17)

尝试将特殊值复用到返回值上通常是一个糟糕的设计......你试图用单个值做太多。通过参数而不是返回值返回“成功指针”会更清晰。这会在您要描述的所有条件的返回值中留下大量非冲突空格:

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

    *p = yourValue;
    return SUCCESS;
}

您还应该进行典型的参数检查(确保'p'不是NULL)。

答案 3 :(得分:5)

C语言没有为指针定义“否定性”的概念。 “否定”的属性主要是算术的,不以任何方式适用于指针类型的值。

如果你有一个指针返回函数,那么你无法从该函数中有意义地返回-1的值。在C语言中,积分值(除零之外)不能隐式转换为指针类型。尝试从指针返回函数返回-1是一种立即约束违规,将导致诊断消息。简而言之,这是一个错误。如果您的编译器允许它,它只是意味着它不会过于严格地强制执行该约束(大多数时候它们都是为了与预标准代码兼容)。

如果通过显式强制转换强制-1的值为指针类型,则强制转换的结果将是实现定义的。语言本身并不能保证它。它可能很容易证明与其他一些有效的指针值相同。

如果要创建保留指针值,则无需malloc任何内容。您可以简单地声明所需类型的全局变量,并使用其地址作为保留值。它保证是独一无二的。

答案 4 :(得分:4)

指针可以是负数,无符号整数可以是负数。也就是说,当然,在二进制补码解释中,您可以将数值解释为负值,因为最重要的位是开启的。

答案 5 :(得分:1)

失败和酉化之间的区别是什么。如果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 */

答案 6 :(得分:0)

@James当然是正确的,但我想补充一点,指针并不总是代表绝对内存地址,理论上它们总是正面的。指针还表示存储器中某些点的相对地址,通常是堆栈或帧指针,并且这些地址可以是正数和负数。

所以最好的办法是让你的函数接受一个指向指针的指针作为参数,并在从实际函数返回结果代码时用成功的指针值填充该指针。

答案 7 :(得分:0)

詹姆斯回答可能是正确的,但当然描述了一个实现选择,而不是你可以做出的选择。

就个人而言,我认为地址是“直觉上”未签名的。查找比较为小于空指针的指针似乎是错误的。但~0-1对于相同的整数类型,给出相同的值。如果它直观地未签名,~0可能会产生更直观的特殊情况值 - 我将它用于错误情况下的无符号整数。它不是真的不同(默认情况下零是一个int,所以~0-1,直到你投射它)但看起来不同。

32位系统上的指针可以使用所有32位BTW,但-1~0是一个极不可能的指针,在实践中是真正的分配。还有特定于平台的规则 - 例如在32位Windows上,一个进程只能有2GB的地址空间,而且有很多代码可以将某种标志编码到指针的最高位(例如用于平衡)平衡二叉树中的标志。)

答案 8 :(得分:0)

实际上,(至少在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;
};

这在某些情况下可能有用。 它不适用于某些哈佛架构,但适用于冯诺依曼架构。

答案 9 :(得分:0)

在这种情况下,NULL是唯一有效的错误返回,无论何时返回无符号值(如指针),都是如此。在某些情况下,指针可能不会大到足以将符号位用作数据位,但由于指针是由OS控制而不是程序,因此我不会依赖此行为。

请记住,指针基本上是32位值;这是否是可能的负数或总是正数仅仅是解释(即)第32位被解释为符号位还是数据位。因此,如果您将0xFFFFFFF解释为有符号数,则它将为-1,如果您将其解释为无符号数,则为4294967295.从技术上讲,指针不太可能如此大,但无论如何都应该考虑这种情况。

就替代方案而言,您可以使用额外的out参数(为所有失败返回NULL),但是这将要求客户端创建并传递值,即使他们不需要区分特定错误。

另一个替代方法是使用GetLastError / SetLastError机制来提供其他错误信息(这将特定于Windows,不知道这是否是一个问题),或者在错误时抛出异常。< / p>

Devin Ellingson

答案 10 :(得分:0)

请勿将malloc用于此目的。它可能会阻止不必要的内存(如果在malloc被调用时已经使用了大量内存并且例如在高地址处分配了sentinel)并且它会混淆内存调试器/泄漏检测器。而是简单地返回指向本地static const char对象的指针。该指针永远不会与程序以任何其他方式获得的任何指针进行比较,并且只会浪费bss的一个字节。

答案 11 :(得分:0)

您不必关心指针的符号性,因为它是定义的实现。真正的问题是“如何从函数返回指针中返回特殊值?” ,我在对问题Pointer address span on various platforms

的回答中已对此进行了详细说明。

总而言之,全比特模式(-1)几乎是安全的,因为它已经在频谱的末尾,并且无法将数据存储在第一个地址周围。它甚至被许多Linux系统调用返回,以指示指针的另一种状态。因此,如果您只需要失败未初始化,那么这是一个不错的选择

但是,通过利用变量必须正确对齐这一事实(除非您指定了其他选项),您可以返回更多错误状态。例如,在指向int32_t的指针中,低2位始终为零,这意味着只有1/3的可能值是有效地址,其余所有位模式供您使用。所以一个简单的解决方案就是只检查最低位

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

您还可以使用高位在64位系统中存储数据。在ARM上有一个标志,告诉CPU忽略地址中的高位。在x86上没有类似的东西,但是只要在取消引用之前使其规范即可,您仍然可以使用这些位。参见Using the extra 16 bits in 64-bit pointers

另请参见

答案 12 :(得分:-1)

正面或负面不是指针类型的有意义的方面。它们属于有符号整数,包括signed 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类型,{{1}生成无符号类型。但是对于指针减法,它会产生一个带符号的类型,因为语义是两个指针之间的距离,单位是元素的数量。

总之,我建议将指针类型视为独立类型,每种类型都有它的操作集。对于指针(排除函数指针,成员函数指针和void *):

  1. 列表项
  2. unsigned_int_a - unsigned_int_b,+ =

    ptr + any_integer_type

  3. +, - =

    ptr - any_integer_type

    ptr1 - ptr2

  4. ++前缀和后缀

  5. - 前缀和后缀
  6. 注意指针没有-操作。这也支持指针应该被视为独立类型,而不是“类似于int的类型”或“基础类型为int的类型,因此它应该看起来像int”。