【问题标题】:Unsigned int from 32 bit to 64bit OS从 32 位到 64 位操作系统的无符号整数
【发布时间】:2014-10-03 10:14:02
【问题描述】:

这段代码 sn-p 摘自一本 linux 书籍。 如果这不适合在此处发布代码 sn-p,请告诉我。 我会删除它。谢谢。

#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 工作但 unsigned 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 在最后一个 printf 处运行到核心转储中。

index-1 = ffffffff (sizeof 4)

Segmentation fault (core dumped)

【问题讨论】:

  • 您的第一个 printf 可能是问题所在。根据月相的结构和相位,printf 期望 %l 值作为两个参数位置传递。
  • 一般来说,您尝试访问的内存可能超出了您的可访问内存范围 - p[index-1] 等于从 p + UINT_MAX 位置读取。
  • 感谢您的 cmets,Hot Licks。 1) 我将 %lx 更改为 %d,它运行到核心转储中。 2)我注释掉整行,只留下最后一个 printf,我仍然遇到核心转储。如果你想给我一些启示,想向你学习吗?谢谢。结果:在 32 位上:index-1 = ffffffff (sizeof 4) A:buf=0xbf8297b6 p=0xbf8297b7 p[-1]=0xbf8297b6 在 64 位上:分段错误(核心转储)
  • 谢谢,冰袋。但是通过将数据类型从 unsigned int 切换为 unsigned long 可以毫无问题地通过。据我了解,p 指向 buf 的第二个元素,即 buf[1]。因此,p[index-1] 访问 buf 的第一个元素应该没有问题。但是,我对数组索引,无符号整数和-1之间的数据转换不是很清楚,因为我认为这可能是导致问题的原因......
  • 使用%d 打印sizeof 的结果是未定义的行为。使用%zu 格式化size_t 类型的对象。

标签: c linux 32bit-64bit


【解决方案1】:

不要对编译器撒谎!

在预期 long (%ld) 的地方传递 printfint 是未定义的行为。
(创建一个指向任何有效对象之外(而不仅仅是在一个对象后面)的指针也是 UB...)

更正格式说明符和指针算法(包括索引作为特殊情况),一切都会正常工作。

UB 包括“它按预期工作”以及“灾难性故障”。

顺便说一句:如果您礼貌地向编译器询问所有警告,它会警告您。使用-Wall -Wextra -pedantic 或类似名称。

【讨论】:

    【解决方案2】:

    另一个问题是代码在您的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.
    

    解决方案很简单:您需要将 long 传递给printf 或告诉printf 打印int

    printf("%lx", (long)(i-100) );
    printf("%x", i-100);
    

    您在 32 位上很幸运,您的应用没有崩溃。将其移植到 64 位后发现代码中存在错误,您现在可以修复它。

    【讨论】:

    • 没错,但不是OP的问题,这是关于第二个printf。 @rickhau:提供最少示例而不是通过 cmets 更新问题(但编辑问题本身)的另一个原因。
    • @mafso 是的,我看到了。谢谢。我更新了我的响应工作,因为它甚至无法修复确切的崩溃,这是其他可能出错的事情(例如 - 如果在 int 之后有一些变量被推送到堆栈,printf 将不会显示它们正确)。
    • 谢谢,@elcuco 即使我在第一个 printf 上使用 %x 而不是 %lx,我似乎仍然会遇到核心转储。
    • @rickhau 并不意外 - 您在数组中引用了一个超出范围的索引。
    【解决方案3】:

    无符号值的算术总是以环绕的方式定义的。例如。 (unsigned)-1UINT_MAX 相同。所以像

    这样的表达式
    p + (0u-1)
    

    等价于

    p + UINT_MAX
    

    &amp;p[0u-1] 等价于&amp;*(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 ☰ ffffffffhex mod 232

    (ffffffffhex 是 232-1 或 UINT_MAX),也是

    ffffffffffffffffhex ☰ ffffffffhex mod 232

    (对于 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)
    

    ffffffffffffffffhex 是 -1 模 264

    • unsigned index = 0;

    int 不能保存任何值 unsigned int 可以,因此在 index - 1 中没有任何内容被提升为 int,结果类型为 unsigned int,值为 -1(,与 UINT_MAX 或 ffffffffhex 相同,因为类型是无符号的)。对于 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,未修改的 xunsigned inthxunsigned short(当传递给 printf 时,unsigned short 被提升为 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),对表达式大小的唯一影响是通常的算术转换,这也是由一元+ 触发的。

    【讨论】:

    • 并非总是如此,只是总是如此。
    • @Deduplicator:我犹豫了一下,可能会导致误解,例如对于 32 位整数,1u &lt;&lt; 32 将被定义为评估为 0,但它是 UB。您是否有更清晰的措辞的建议,而不需要用不相关的主题来夸大答案?
    • 算术。位移不是算术。
    • 感谢您的 cmets @mafso。我感到困惑的是p + (0u -1) == p + UINT_MAX 当索引是无符号整数时。但是,p + (0ul - 1) == p[-1] 当索引为无符号长时。我被困在这里了。
    • 转化被定义为保值。如果这是不可能的,对于未签名的目标类型有环绕,对于有符号的目标类型,你会得到实现定义的值或信号。
    猜你喜欢
    • 2016-05-08
    • 2013-08-29
    • 1970-01-01
    • 2011-02-20
    • 2011-12-18
    • 1970-01-01
    • 2013-10-31
    • 2011-07-06
    • 2012-09-01
    相关资源
    最近更新 更多