我有一个函数,希望它能返回特殊的值来表示失败和未初始化(成功时返回指针)。
目前它在失败时返回NULL
,在未初始化时返回-1
,这似乎起作用...但我可能在欺骗系统。如果我没记错,地址始终是正数,不是吗?(尽管编译器允许我将地址设置为-1,这似乎很奇怪)。
[更新]
我另一个想法(如果-1是有风险的)是在全局范围内使用malloc
分配一个字符@
,并将该地址用作哨兵。
我有一个函数,希望它能返回特殊的值来表示失败和未初始化(成功时返回指针)。
目前它在失败时返回NULL
,在未初始化时返回-1
,这似乎起作用...但我可能在欺骗系统。如果我没记错,地址始终是正数,不是吗?(尽管编译器允许我将地址设置为-1,这似乎很奇怪)。
我另一个想法(如果-1是有风险的)是在全局范围内使用malloc
分配一个字符@
,并将该地址用作哨兵。
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)。
-1
。在C语言中,整数值(除零外)不能隐式转换为指针类型。尝试从返回指针的函数返回-1
是一种立即约束违规,这将导致诊断消息。简而言之,这是一个错误。如果编译器允许它,那么它只意味着它并未严格执行该约束(大多数时候,它们为了与预标准代码兼容而这样做)。-1
的值强制转换为指针类型,则转换结果将是实现定义的。语言本身对此不作任何保证。它可能很容易被证明与其他有效的指针值相同。malloc
任何内容。您可以简单地声明所需类型的全局变量,并使用其地址作为保留值。这是保证唯一的。失败和未初始化之间有什么区别。如果未初始化不是另一种失败类型,那么您可能需要重新设计接口以将这两种情况分开。
最好的方法可能是通过参数返回结果,以便返回值仅指示错误。例如,您原本会写成:
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 */
int* x = NULL;
x[10] = 1;
因此,有更多的地址在解引用时保证会生成空指针异常。 现在考虑这段代码(为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。const char
对象永远不会像字符串字面量那样合并吗?(如果你确定,也许你想回答一下我在这个问题上的提问,我认为它没有真正的共识。) - icktoofay@James 是正确的,但我想补充一点,指针并不总是代表绝对的内存地址,理论上这些地址总是正数。指针也可以表示相对于某个内存点的地址,通常是堆栈或帧指针,这些地址既可以是正数也可以是负数。
因此,最好让您的函数接受一个指向指针的参数,并在成功时将该指针填充为有效的指针值,同时从实际函数返回结果代码。
James的回答可能是正确的,但当然描述了一种实现选择,而不是你可以做出的选择。
个人认为,地址在直觉上是无符号的。找到一个与空指针比较小的指针似乎是错误的。但是,对于相同的整数类型,~0
和-1
给出相同的值。如果它在直觉上是无符号的,~0
可能会产生更直观的特殊值 - 我经常将其用于错误情况下的无符号整数。它并不是真正不同(默认情况下零是int,因此~0
是-1
,直到你将其转换),但它看起来不同。
顺便说一句,在32位系统上,指针可以使用所有32位,尽管在实践中,-1
或~0
是极不可能发生的真实分配指针。还有平台特定的规则 - 例如,在32位Windows上,进程只能拥有2GB的地址空间,并且有很多代码将某种标志编码到指针的最高位中(例如,用于平衡二叉树中的平衡标志)。
int* foo(){ return -1;}
会产生警告“warning: return makes pointer from integer without a cast”。我不确定这证明或反驳了你的观点,但当你说“C编译器不在乎你给它什么,它们会尝试编译任何东西”时,我仍然感到恼火。(顺便说一下,在g++中,这是一个错误error: invalid conversion from ‘int’ to ‘int*’
。) - Ken Bloom