首先让我先声明,我知道 char
、signed char
和 unsigned char
在 C++ 中是不同的类型。从标准的快速阅读来看,char
是否 signed
似乎也是实现定义的。为了让事情变得更有趣,g++
似乎决定了 char
是否在每个平台的基础上被签名
!
所以不管怎样,在那个背景下,让我介绍一个我在使用这个玩具程序时遇到的错误:
#include <stdio.h>
int main(int argc, char* argv[])
{
char array[512];
int i;
char* aptr = array + 256;
for(i=0; i != 512; i++) {
array[i] = 0;
}
aptr[0] = 0xFF;
aptr[-1] = -1;
aptr[0xFF] = 1;
printf("%d\n", aptr[aptr[0]]);
printf("%d\n", aptr[(unsigned char)aptr[0]]);
return 0;
}
预期的行为是对 printf
的两次调用都应输出 1。当然,在 gcc
和 g++ 4.6.3
上会发生什么在 linux/x86_64
上运行是第一个 printf
输出 -1 而第二个输出 1。这与 chars 被签名和 g++
解释一致-1 的负数组索引(这在技术上是未定义的行为)是明智的。
这个错误似乎很容易修复,我只需要将 char
转换为 unsigned
,如上所示。我想知道的是这段代码是否曾被期望在使用 gcc/g++
的 x86 或 x86_64 机器上正常工作?看起来这可能在 ARM 平台上按预期工作,显然 chars 是无符号的,但我想知道这段代码在使用 g++
的 x86 机器上是否一直存在错误?
最佳答案
我在你的程序中没有看到未定义的行为。负数组索引不一定无效,只要将索引添加到前缀的结果指向有效的内存位置即可。 (如果前缀是数组对象的名称或指向数组对象第 0 个元素的指针,则负数组索引是无效的(即具有未定义的行为),但这里不是这种情况。)
在这种情况下,aptr
指向 512 元素数组的元素 256,因此有效索引从 -256 到 +255(+256 产生刚好超过数组末尾的有效地址,但它不能被取消引用)。假设 CHAR_BIT==8
, signed char
、 unsigned char
或普通 char
中的任何一个都有一个范围,该范围是数组有效索引范围的子集。
如果 plain char
被签名,那么:
aptr[0] = 0xFF;
会将 int
值 0xFF
(255
) 隐式转换为 char
,并且该转换的结果是实现定义的——但它将在普通 char
的范围内,并且几乎肯定是 -1
。如果 plain char
是无符号的,那么它会将值 255
分配给 aptr[0]
。因此,代码的行为取决于普通 char
的签名(并且可能取决于实现定义的将超出范围的值转换为签名类型的结果),但没有未定义的行为。
(从 C99 开始,将超出范围的值转换为带符号的类型也可能会引发实现定义的信号,但我知道没有任何实现会真正做到这一点。在将 0xFF
转换为时引发信号char
可能会破坏现有代码,因此编译器编写者极力避免这样做。)
关于c++ - g++/gcc 中 char 的符号及其历史,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27803749/