例如,
char * integerToString(void);
int main() {
char *myString;
do {
myString = integerToString();
} while (myString == (char *)-1); // worked as intended
free(myString);
return 0;
}
char * integerToString(void) {
int userInput;
printf("Enter an integer: ");
scanf("%d", &userInput);
if (userInput < 0 || userInput > 99)
return (char *)-1; // what happens here?
char *myString = (char *)malloc(sizeof(char) * 2);
myString[0] = (int)floor(userInput/10.0) + '0';
myString[1] = userInput%10 + '0';
return myString;
}
程序按预期工作,但是当您将整数值(不将整数分配给变量)键入字符指针时,究竟会发生什么?这个程序会一直有效吗? 谢谢。
C99:
6.3.2.3 指针
- 值为 0 的整数常量表达式,或转换为类型的此类表达式
void *
,称为空指针常量。如果将空指针常量转换为 指针类型,生成的指针,称为空指针,保证比较不相等 指向任何对象或函数的指针。[...]
- 整数可以转换为任何指针类型。除非前面指定, 结果是实现定义的,可能未正确对齐,可能未指向 引用类型的实体,并且可能是陷阱表示形式。
因此,将-1
转换为指针具有实现定义的结果。因此,答案是否定的:这不能保证一般有效。
特别是:如果它确实被证明是一个陷阱表示,你的代码就会违反:
6.2.6 类型的表示
6.2.6.1 概述
[...]
某些对象表示
- 不需要表示对象类型的值。如果存储 对象的值具有这样的表示形式,并由 lvalue 表达式读取 没有字符类型,行为未定义。如果产生了这样的表示 通过左值表达式修改对象的全部或任何部分的副作用 没有字符类型,行为未定义。这样的表示称为陷阱表示形式。
即 如果while (myString == (char *)-1);
是陷阱表示形式myString
则具有未定义的行为。
此程序是错误处理不当的一个例子。(char *)-1
的值似乎是实现定义的,请参阅其他答案。由于此地址可能不是从malloc
返回的有效内存地址,因此在程序中用作哨兵值。实际值不感兴趣,它与其他函数中的相同表达式进行比较。
如果运行此值,malloc
可能会返回(char *)-1
计算的任何值。然后它将被解释为错误,尽管它是一个有效的内存地址。
更好的方法是有一个参数来integerToString
类型int *
并将其用作布尔值来指示失败。这样就不会为错误处理保留一个char *
值。
或者使用C++和例外。
将整数值转换为字符指针时会发生什么情况?
一般来说,这是未定义的行为(至少在你取消引用它后)。非常害怕。阅读更多关于UB的信息(这是一个棘手的主题)。
在某些记录的情况下,您可以将uintptr_t
或intptr_t
整数值放入有效指针中。
在您的情况下,堆分配的字符串太短(因此您有一个缓冲区溢出,这是 UB 的众多示例之一)。您忘记了终止NUL字节的空间,并且忘记检查malloc
失败。顺便说一句,sizeof(char)
始终为 1。
您可以编写代码:
if (userInput < 0 || userInput > 99)
return NULL;
char *myString = (char *)malloc(3);
if (!myString) { perror("malloc myString"); exit(EXIT_FAILURE); };
myString[0] = (int)floor(userInput/10.0) + '0';
myString[1] = userInput%10 + '0';
myString[2] = (char)0;
return myString;
在大多数系统(但不是全部)上,(char*)-1
从来都不是有效的地址(总是在虚拟地址空间之外),并且永远不能由系统(或标准)函数提供。在我的 Linux/x86-64 桌面上,我知道(char*)-1
不是一个有效的地址(例如,因为它是MAP_FAILED
),我可以(有时)将其用作哨兵非空指针值(不应取消围栏)。但这会使我的代码的可移植性降低。
因此,您可以决定并记录您的integerToString
对非整数输入(char*)-1
,在堆分配失败时NULL
。这可以在我的 Linux/x86-64 桌面上工作(所以我有时会这样做)。但这不是纯(可移植)C11代码。
但是,如果您坚持 C11 标准(阅读 n1570),则实现定义了什么以及(char*)-1
是否有意义。它可能是一些陷阱表示,你甚至不允许比较(即使我不知道任何实际的 C 实现这样做)。
实际上,你的例子说明了人们从不为纯标准的C11编码;他们总是(我也是)对C实现做出额外的假设;但你确实需要意识到它们,这些假设可能会使你的代码移植到某个假设的未来机器上成为一场噩梦。
这个程序会一直有效吗?
这是一个太笼统的问题。您的原始程序甚至没有处理malloc
故障并且存在缓冲区溢出(因为您忘记了终止零字节的空间)。然而,对你来说可悲的是,它显然经常起作用(这就是为什么UB如此可怕)。然而,考虑这一点(符合标准,但不现实)malloc
实施作为思考的食粮。
(确切地解释为什么你的程序看起来像你想要的那样真的很困难,因为你需要深入研究几个实现细节)