首先让我先说我知道char
,signed char
和unsigned char
是C ++中的不同类型。通过快速阅读标准,似乎char
是signed
是否是实现定义的。为了让事情变得更有趣,我们会g++
确定char
是否基于每个平台signed
!
所以无论如何,在这个背景下,让我介绍一下我使用这个玩具程序遇到的一个错误:
#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而第二个输出1.这与被签名的字符和g++
明确地解释-1的负数组索引(这是技术上未定义的行为)一致。
该错误似乎很容易修复,我只需要将char
转换为unsigned
,如上所示。我想知道的是,是否希望此代码在使用gcc/g++
的x86或x86_64计算机上正常工作?看起来这可能在ARM平台上有效,显然字符是无符号的,但是我想知道这个代码在使用g++
的x86机器上是否总是有问题?
答案 0 :(得分:4)
我在程序中看不到任何未定义的行为。负数组索引不一定无效,只要将索引添加到前缀的结果是指有效的内存位置即可。 (如果前缀是数组对象的名称或指向数组对象的第0个元素的指针,则负数组索引无效(即,具有未定义的行为),但这不是这种情况。)
在这种情况下,aptr
指向512个元素数组的元素256,因此有效索引从-256到+255(+256产生一个有效的地址,刚好超过数组的末尾,但是它不能被解除引用)。假设CHAR_BIT==8
,signed char
,unsigned char
或普通char
中的任何一个都有一个范围,该范围是数组有效索引范围的子集。
如果签署了普通char
,那么:
aptr[0] = 0xFF;
会隐式将int
值0xFF
(255
)转换为char
,并且该转换的结果是实现定义的 - 但它将在普通char
的范围,几乎肯定是-1
。如果普通char
未签名,则会将值255
分配给aptr[0]
。因此,代码的行为取决于普通char
的签名(可能还有实现定义的超出范围值转换为有符号类型的结果),但没有未定义的行为。
(将一个超出范围的值转换为带符号的类型也可以从C99开始,引发一个实现定义的信号,但我知道没有实现这样做的实现。在{{的转换上引发一个信号1}}到0xFF
可能会破坏现有的代码,因此编译器编写者非常积极地不这样做。)
答案 1 :(得分:1)
数组的类型与索引无关(底层内存访问除外)。
例如:
signed int a[25];
unsigned int b[25];
int value = a[-1];
unsigned int u_value = b[-5];
两种情况的索引公式为:
memory_address = starting_address_of_array
+ index * sizeof(array_type);
就char
而言,无论是(根据语言规范的定义),它的大小都是1。
算术表达式中char
的用法可能取决于它是有符号还是无符号。
答案 2 :(得分:0)
预期行为是对printf的两次调用都应该输出1
你确定吗?
aptr [0]的rval是一个带符号的char,它是-1,它再次用于索引到aptr [],因此第一个printf()得到的是-1。
同样适用于第二个printf但是在那里,使用类型转换确保它被解释为unsigned char,因此你最终得到255,并使用它来索引到aptr []你得到1来自第二个printf()。
我相信你对预期行为的假设是不正确的。
编辑1:
看起来这显然可以在ARM平台上运行 字符是无符号的,但我想知道这段代码是否总是如此 在使用g ++的x86机器上有错误吗?
基于此声明,您似乎知道x86上的char已签名(与某些人假设您所假设的内容相对)。因此我提供的解释应该是好的,即将char视为x86上的signed char。
编辑2:
只要指针使用负数组索引就完全没问题了 操作数是一个内部元素: stackoverflow.com/questions/3473675/negative-array-indexes-in-c - ecatmur
这是@ecatmur对问题的评论之一。这说明负面指数与某些人的想法相反是好的。
答案 3 :(得分:0)
您的printf语句与以下内容相同:
printf("%d\n", aptr[(char)255]);
printf("%d\n", aptr[(unsigned char)(char)255]);
因此显然取决于平台在这些转换中的行为。
我想知道的是,这个代码是否可以在使用gcc / g ++的x86或x86_64机器上正常工作?
正确地考虑&#39;表示您描述的行为,不,这应该永远不会在char
签名的平台上以这种方式表现。
当char
签名(并且不能代表255)时,您将获得一个实现定义的值,并且在可表示的范围内。对于8位,2的补码表示,这意味着您在[-128,127]范围内得到一些值。这意味着唯一可能的输出:
printf("%d\n", aptr[(char)255]);
&#34; 0&#34;和&#34; -1&#34; (忽略printf
失败的情况)。常见的实现定义了转换结果打印&#34; -1&#34;。
代码定义良好但在定义不同char
签名的实现之间无法移植。编写可移植代码包括不依赖于char
被签名或无符号,这反过来意味着如果索引被限制在[0,127]范围内,则只应使用char
值作为数组索引。