此代码片段摘自一本 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]=%pn", p[index-1], buf, p, &p[index-1]);
return 0;
}
在 32 位操作系统环境中:无论索引的数据类型是无符号整数还是无符号长整型,该程序都可以正常工作。
在 64 位操作系统环境中:如果索引声明为无符号 int,则同一程序将运行到"核心转储"。但是,如果我只将索引的数据类型从无符号 int 更改为 a( 无符号长整型或 b( 无符号短,该程序也可以正常工作。
书中的原因只告诉我,由于非负数,64位会导致核心转储。但是我不知道为什么没有签名的长和无符号的短作品而没有签名的int。
我感到困惑的是
当索引为无符号 int 时p + (0u -1) == p + UINT_MAX
。
但
当索引为无符号长整型时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
在最后一次打印时遇到核心转储。
index-1 = ffffffff (sizeof 4)
Segmentation fault (core dumped)
不要对编译器撒谎!
printf
期望long
(%ld
(的int
传递是未定义的行为。
(创建一个指向任何有效对象外部(而不仅仅是一个对象后面(的指针也是 UB ......
更正格式说明符和指针算法(包括索引作为特殊情况(,一切正常。
UB 包括"它按预期工作"以及"灾难性故障"。
顺便说一句:如果你礼貌地要求编译器提供所有警告,它会警告你。使用-Wall -Wextra -pedantic
或类似用途。
问题是代码在你的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 位会在您的代码中发现一个错误,您现在可以修复它。
无符号值的算术总是以环绕方式定义的。 例如 (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
您始终可以将无符号类型的加法、减法和乘法的操作数替换为全等取模最大值 + 1。例如
-1 ☰ ffffffffhex mod 232
(ffffffff十六进制是 232-1 或 UINT_MAX
(,并且
ffff ☰
(对于 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)
fffff
-
unsigned index = 0;
int
不能保存任何值unsigned int
可以,因此index - 1
没有任何东西被提升为int
,结果的类型为 unsigned int
和值 -1(这是正数,与 UINT_MAX
或 ffffffff十六进制相同,因为类型是无符号的(。对于 32 位地址,添加此值与减去 1 相同:
bfbdd5db bfbdd5db
+ ffffffff - 1
= 1bfbdd5da
= bfbdd5da = bfbdd5da
(请注意环绕/截断。但是,对于 64 位地址:
00007fff b81d6c21
+ ffffffff
= 00008000 b81d6c20
没有环绕。这是在尝试访问无效地址,因此出现段错误。
也许看看维基百科上的2的补充。
在我的 64 位 Linux 下,使用一个期望 32 位值的说明符同时传递一个 64 位类型(反之亦然(似乎"有效",只读取 32 个最低有效位。但是使用正确的。 lx
期望一个unsigned long
,未修改x
unsigned int
,hx
一个unsigned short
(由于默认参数提升,unsigned short
在传递给printf
时被提升为int
(它作为变量参数传递(。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)
,对表达式大小的唯一影响是通常的算术转换,这也是由一元+
触发的。