从32位到64位OS的无符号整数

时间:2014-08-10 12:52:45

标签: c linux 32bit-64bit

此代码段摘自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)

3 个答案:

答案 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)-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

您总是可以用无效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被提升为intindex - 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 inthxunsigned shortunsigned short被提升为{{1}传递给int时(它作为变量参数传递),由于默认参数提升)。 printf的长度修饰符为size_t,与z

相同
%zu

(转换为printf("index-1 = %lx (sizeof %zu)\n", (unsigned long)(index-1), sizeof(index-1)); 不会更改unsigned longunsigned intunsigned short表达式的值。)

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