c++ - g++/gcc 中 char 的符号及其历史

标签 c++ gcc char g++

首先让我先声明,我知道 charsigned charunsigned 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。当然,在 gccg++ 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==8signed charunsigned char 或普通 char 中的任何一个都有一个范围,该范围是数组有效索引范围的子集。

如果 plain char 被签名,那么:

aptr[0] = 0xFF;

会将 int0xFF (255) 隐式转换为 char ,并且该转换的结果是实现定义的——但它将在普通 char 的范围内,并且几乎肯定是 -1 。如果 plain char 是无符号的,那么它会将值 255 分配给 aptr[0] 。因此,代码的行为取决于普通 char 的签名(并且可能取决于实现定义的将超出范围的值转换为签名类型的结果),但没有未定义的行为。

(从 C99 开始,将超出范围的值转换为带符号的类型也可能会引发实现定义的信号,但我知道没有任何实现会真正做到这一点。在将 0xFF 转换为时引发信号char 可能会破坏现有代码,因此编译器编写者极力避免这样做。)

关于c++ - g++/gcc 中 char 的符号及其历史,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27803749/

相关文章:

c++ - 删除 C++ 时哈希键冲突

c++ - istreambuf_iterator 什么时候抛出异常?

c - 在 C 中检测 64 位编译

c++ - 让 GCC 在 C++11 模式下在 FreeBSD 上工作

c++ - 无法理解 C 中字符串和循环的这种意外行为

c - 从文本文件中删除该行的最后一个成员

c++ - 在 C++ 中将模板对象自动转换为模板指针

c++ - 使用 C++11,我还需要一个用于 Unicode 文本的非标准字符串操作库吗?

c++ - 将 unsigned short/int 转换为 char *

c++ - 更改/访问点云的原点和方向