C语言 从 32 位到 64 位操作系统的无符号 int



此代码片段摘自一本 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)-1UINT_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"

假设 shortintlong 分别为 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-1UINT_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被提升为intindex - 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 inthx一个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 intunsigned shortunsigned char表达式的值。

sizeof(index-1)也可以写成sizeof(+index),对表达式大小的唯一影响是通常的算术转换,这也是由一元+触发的。

最新更新