此代码段摘自linux书籍。 如果这不适合在此处发布代码段,请告知我们。 我会删除它。感谢。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char buf[30];
char *p;
int i;
unsigned int index = 0;
//unsigned long index = 0;
printf("index-1 = %lx (sizeof %d)\n", index-1, sizeof(index-1));
for(i = 'A'; i <= 'Z'; i++)
buf[i - 'A'] = i;
p = &buf[1];
printf("%c: buf=%p p=%p p[-1]=%p\n", p[index-1], buf, p, &p[index-1]);
return 0;
}
在32位操作系统环境中: 无论索引的数据类型是unsigned int还是unsigned long,这个程序都能正常工作。
在64位操作系统环境中: 如果index声明为unsigned int,则同一程序将进入“核心转储”。 但是,如果我只将索引的数据类型从unsigned int更改为a)unsigned long或b)unsigned short, 这个程序也可以。
本书的原因只告诉我64位因非负数而导致核心转储。但是我不知道为什么unsigned long和unsigned short work的原因是unsigned int。
我感到困惑的是
索引为unsigned int时 p + (0u -1) == p + UINT_MAX
。
但是,
当index为unsigned long时, p + (0ul - 1) == p[-1]
。
我被困在这里。
如果有人可以帮助详细说明细节,我们非常感谢!
我的32位(RHEL5.10 / gcc版本4.1.2 20080704)有一些结果
和64位机器(RHEL6.3 / gcc版本4.4.6 20120305)
我不确定gcc版本在这里是否有任何区别。 所以,我也粘贴了这些信息。
开32位:
我尝试了两项修改:
1)将unsigned int index = 0
修改为unsigned short index = 0
。
2)将unsigned int index = 0
修改为unsigned char index = 0
。
程序可以毫无问题地运行。
index-1 = ffffffff (sizeof 4)
A: buf=0xbfbdd5da p=0xbfbdd5db p[-1]=0xbfbdd5da
由于-1,索引的数据类型似乎将被提升为4个字节。
64位:
我尝试了三个更改:
1)将unsigned int index = 0
修改为unsigned char index = 0
。
It works!
index-1 = ffffffff (sizeof 4)
A: buf=0x7fffef304ae0 p=0x7fffef304ae1 p[-1]=0x7fffef304ae0
2)将unsigned int index = 0
修改为unsigned short index = 0
。
It works!
index-1 = ffffffff (sizeof 4)
A: buf=0x7fff48233170 p=0x7fff48233171 p[-1]=0x7fff48233170
3)将unsigned int index = 0
修改为unsigned long index = 0
。
It works!
index-1 = ffffffff (sizeof 8)
A: buf=0x7fffb81d6c20 p=0x7fffb81d6c21 p[-1]=0x7fffb81d6c20
但是,只有
unsigned int index = 0
在最后一个printf进入核心转储。
index-1 = ffffffff (sizeof 4)
Segmentation fault (core dumped)
答案 0 :(得分:1)
不要欺骗编译器!
将printf
int
传递到期望long
(%ld
)的位置是未定义的行为。
(创建指向任何有效对象外部的指针(而不仅仅是一个)也是UB ...)
更正格式说明符和指针算术(包括作为特殊情况的索引),一切都会有效。
UB包括“它按预期工作”以及“灾难性故障”。
顺便说一句:如果你礼貌地问你的编译器是否有所有警告,它会警告你。使用-Wall -Wextra -pedantic
或类似内容。
答案 1 :(得分:1)
另一个问题是代码存在于printf()
:
printf("index-1 = %lx (sizeof %d)\n", index-1, sizeof(index-1));
让我们简化:
int i = 100;
print("%lx", i-1);
您说printf
这里是long
,但实际上您正在发送int
。 clang确实告诉你正确的警告(我认为gcc也应该吐出正确的警告)。参见:
test1.c:6:19: warning: format specifies type 'unsigned long' but the argument has type 'int' [-Wformat]
printf("%lx", i - 100);
~~~ ^~~~~~~
%x
1 warning generated.
解决方案很简单:您需要将长传递给printf
或告诉printf
打印int
:
printf("%lx", (long)(i-100) );
printf("%x", i-100);
你在32位运气好,你的应用程序没有崩溃。将其移植到64位会显示您的代码中存在错误,您现在可以修复它。
答案 2 :(得分:-1)
就环绕而言,总是定义无符号值的算术。例如。 (unsigned)-1
与UINT_MAX
相同。所以像
p + (0u-1)
相当于
p + UINT_MAX
(&p[0u-1]
相当于&*(p + (0u-1))
和p + (0u-1)
)。
如果我们用无符号整数类型替换指针,也许这更容易理解。考虑:
uint32_t p32; // say, this is a 32-bit "pointer"
uint64_t p64; // a 64-bit "pointer"
假设short
,int
和long
分别为16位,32位和64位(同一行上的条目相等):
p32 + (unsigned short)-1 p32 + USHRT_MAX p32 + (UINT_MAX>>16)
p32 + (0u-1) p32 + UINT_MAX p32 - 1
p32 + (0ul-1) p32 + ULONG_MAX p32 + UINT_MAX p32 - 1
p64 + (0u-1) p64 + UINT_MAX
p64 + (0ul-1) p64 + ULONG_MAX p64 - 1
您总是可以用无效modulo最大值+ 1替换无符号类型的加法,减法和乘法的操作数。例如,
-1☰ffffffff hex mod 2 32
(ffffffff hex 是2 32 -1或UINT_MAX
),还
fffffffffffffffff hex ☰ffffff hex mod 2 32
(对于32位无符号类型,您始终可以截断为最不重要的8个十六进制数字。)
您的示例:
32位
unsigned short index = 0;
在index - 1
中,索引会提升为int
。结果的类型为int
,值为-1(为负数)。与unsigned char
相同。
64位
unsigned char index = 0;
unsigned short index = 0;
与32位相同。 index
被提升为int
,index - 1
为负数。
unsigned long index = 0;
输出
index-1 = ffffffff (sizeof 8)
很奇怪,这是你唯一正确使用的%lx
,但看起来你用%x
打印了它(期待4个字节);在我的64位计算机上(64位long
)和%lx
我得到:
index-1 = ffffffffffffffff (sizeof 8)
ffffffffffffffff hex 是-1 modulo 2 64 。
unsigned index = 0;
int
无法保留任何值unsigned int
,因此在index - 1
中没有任何内容提升为int
,结果的类型为unsigned int
,值为-1 (正,与UINT_MAX
或ffffffff hex 相同,因为类型是无符号的)。对于32位地址,添加此值与减去一个值相同:
bfbdd5db bfbdd5db
+ ffffffff - 1
= 1bfbdd5da
= bfbdd5da = bfbdd5da
(注意环绕/截断。)但是对于64位地址:
00007fff b81d6c21
+ ffffffff
= 00008000 b81d6c20
没有环绕。这是在尝试访问无效地址,因此您会遇到段错误。
也许看看2’s complement on Wikipedia。
在我的64位Linux下,使用指定32位值而在传递64位类型(反之亦然)的说明符似乎“工作”时,只读取32个最低有效位。但要使用正确的。 lx
预计unsigned long
,未修改x
unsigned int
,hx
和unsigned short
(unsigned short
被提升为{{1}传递给int
时(它作为变量参数传递),由于默认参数提升)。 printf
的长度修饰符为size_t
,与z
:
%zu
(转换为printf("index-1 = %lx (sizeof %zu)\n", (unsigned long)(index-1), sizeof(index-1));
不会更改unsigned long
,unsigned int
或unsigned short
表达式的值。)
unsigned char
也可以写为sizeof(index-1)
,对表达式大小的唯一影响是通常的算术转换,它也由一元sizeof(+index)
触发。