我有一个函数,我希望能够为失败和未初始化返回特殊值(它返回成功时的指针)。
目前它返回NULL失败和-1未初始化,这似乎工作......但我可能在欺骗系统。IIRC,地址总是积极的,不是吗?(尽管由于编译器允许我将地址设置为 -1,这似乎很奇怪)。
[更新]
我的另一个想法(如果 -1 有风险)是将mallocchar@设置为全局范围,并将该地址用作哨兵。
我有一个函数,我希望能够为失败和未初始化返回特殊值(它返回成功时的指针)。
目前它返回NULL失败和-1未初始化,这似乎工作......但我可能在欺骗系统。IIRC,地址总是积极的,不是吗?(尽管由于编译器允许我将地址设置为 -1,这似乎很奇怪)。
我的另一个想法(如果 -1 有风险)是将mallocchar@设置为全局范围,并将该地址用作哨兵。
不,地址并不总是正的——在 x86_64 上,指针是符号扩展的,地址空间对称地聚集在 0 周围(尽管“负”地址通常是内核地址)。
然而,这一点主要是没有意义的,因为 C 只定义了指向同一对象的一部分的指针的含义<和>指针之间的比较,或者指向数组末尾的指针之间的比较。除了完全相等之外,指向完全不同对象的指针无法进行有意义的比较,至少在标准 C 中 -if (p < NULL)没有明确定义的语义。
您应该创建一个具有静态存储持续时间的虚拟对象,并将其地址用作您的unintialised值:
extern char uninit_sentinel;
#define UNINITIALISED ((void *)&uninit_sentinel)
保证在您的程序中拥有一个唯一的地址。
指针的有效值完全依赖于实现,因此,是的,指针地址可能为负数。
然而,更重要的是,考虑(作为可能的实现选择的示例)您在具有 32 位指针大小的 32 位平台上的情况。可以由该 32 位值表示的任何值都可能是有效指针。除了空指针,任何指针值都可能是指向对象的有效指针。
对于您的特定用例,您应该考虑返回状态代码,并可能将指针作为函数的参数。
尝试将特殊值多路复用到返回值通常是一种糟糕的设计……您试图用单个值做太多事情。通过参数而不是返回值返回“成功指针”会更干净。对于您要描述的所有条件,这会在返回值中留下大量非冲突空间:
int SomeFunction(SomeType **p)
{
*p = NULL;
if (/* check for uninitialized ... */)
return UNINITIALIZED;
if (/* check for failure ... */)
return FAILURE;
*p = yourValue;
return SUCCESS;
}
您还应该进行典型的参数检查(确保“p”不为 NULL)。
C 语言没有为指针定义“负性”的概念。“为负”的属性主要是算术属性,不适用于指针类型的值。
如果您有一个指针返回函数,那么您无法-1从该函数有意义地返回值。在 C 语言中,整数值(非零)不能隐式转换为指针类型。从指针返回函数返回的尝试-1是立即违反约束,将导致诊断消息。简而言之,这是一个错误。如果您的编译器允许它,它只是意味着它不会过于严格地执行该约束(大多数时候他们这样做是为了与预标准代码兼容)。
如果-1通过显式强制转换将值强制为指针类型,则转换结果将由实现定义。语言本身对此不做任何保证。它可能很容易证明与其他一些有效的指针值相同。
如果要创建保留的指针值,则无需malloc任何操作。您可以简单地声明所需类型的全局变量并将其地址用作保留值。它保证是唯一的。
指针可以是负数,就像无符号整数可以是负数一样。也就是说,当然,在二进制补码解释中,您可以将数值解释为负数,因为最高有效位已打开。
失败和统一化有什么区别。如果 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 */
@James 当然是正确的,但我想补充一点,指针并不总是代表绝对内存地址,理论上它总是积极的。指针还表示内存中某个点的相对地址,通常是堆栈或帧指针,它们可以是正数也可以是负数。
因此,最好的办法是让您的函数接受指向指针的指针作为参数,并在成功时用有效的指针值填充该指针,同时从实际函数返回结果代码。
詹姆斯的回答可能是正确的,但当然描述的是实现选择,而不是您可以做出的选择。
就个人而言,我认为地址是“直观地”未签名的。找到一个比较小于空指针的指针似乎是错误的。但是~0和-1,对于相同的整数类型,给出相同的值。如果它是直观无符号的,~0则可能会产生更直观的特殊情况值 - 我经常将它用于错误情况无符号整数。它并没有真正的不同(默认情况下,零是一个 int,所以~0在-1你强制转换之前也是如此),但它看起来不同。
顺便说一句,32位系统上的指针可以-1使用所有 32 位,或者~0实际上是极不可能发生的真正分配的指针。还有一些特定于平台的规则——例如在 32 位 Windows 上,一个进程只能有 2GB 的地址空间,并且有很多代码将某种标志编码到指针的最高位(例如,用于平衡平衡二叉树中的标志)。
实际上,(至少在 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;
};
这在某些情况下可能很有用。它不适用于某些哈佛架构,但适用于冯诺依曼架构。
请勿malloc用于此目的。它可能会占用不必要的内存(例如,如果在malloc调用时已经使用了大量内存并且哨兵被分配在高地址)并且它会混淆内存调试器/泄漏检测器。而是简单地返回一个指向本地static const char对象的指针。这个指针永远不会与程序可以通过任何其他方式获得的任何指针进行比较,它只会浪费一个字节的 bss。
您不需要关心指针的符号,因为它是实现定义的。这里真正的问题是“如何从函数返回指针返回特殊值?” 我已经在对各种平台上的指针地址跨度问题的回答中详细解释了这一点
总之,全一位模式(-1)(几乎)总是安全的,因为它已经处于频谱的末端,并且数据不能存储到第一个地址,并且该malloc族永远不会返回-1。事实上,这个值甚至被许多 Linux 系统调用和 Win32 API 返回,以指示指针的另一种状态。因此,如果您只需要失败且未初始化,那么这是一个不错的选择
但是您可以利用变量必须正确对齐的事实返回更多的错误状态(除非您指定了一些其他选项)。例如,在指向int32_t低 2 位的指针中始终为零,这意味着只有 ¹⁄₄ 的可能值是有效地址,剩下的所有位模式供您使用。所以一个简单的解决方案就是检查最低位
int* result = func();
if (!result)
error_happened();
else if ((uintptr_t)result & 1)
uninitialized();
在这种情况下,您可以同时返回一个有效指针和一些附加数据
您还可以使用高位在 64 位系统中存储数据。在 ARM 上,有一个标志告诉 CPU 忽略地址中的高位。在 x86 上没有类似的东西,但您仍然可以使用这些位,只要在取消引用之前使其成为规范即可。请参阅在 64 位指针中使用额外的 16 位
也可以看看
NULL是在这种情况下唯一有效的错误返回,任何时候返回无符号值(例如指针)都是如此。在某些情况下,指针可能不足以将符号位用作数据位,但由于指针由操作系统而不是程序控制,因此我不会依赖这种行为。
请记住,指针基本上是一个 32 位的值;这是否是可能的负数或始终为正数只是解释问题(即)第 32位是否被解释为符号位或数据位。因此,如果您将 0xFFFFFFF 解释为有符号数,则为 -1,如果将其解释为无符号数,则为 4294967295。从技术上讲,指针不可能这么大,但无论如何都应该考虑这种情况。
作为替代方案,您可以使用额外的out参数(对所有失败返回NULL),但是这将要求客户端创建并传递一个值,即使他们不需要区分特定错误。
另一种选择是使用GetLastError/SetLastError机制来提供额外的错误信息(这将特定于 Windows,不知道这是否是一个问题),或者在错误时引发异常。
正或负不是指针类型的有意义的方面。它们与有符号整数有关,包括有符号字符、短整数、整数等。
人们谈论负指针主要是在将指针的机器表示视为整数类型的情况下。例如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 *):
+,+=
ptr + any_integer_type
-,-=
ptr - any_integer_type
ptr1 - ptr2
++前缀和后缀
--前缀和后缀注意没有/ * %指针操作。这也支持指针应被视为独立类型,而不是“类似于 int 的类型”或“基础类型为 int 的类型,因此它应该看起来像 int”。