【问题标题】:Why does 32 bit compiler and 64 bit compiler makes such a difference with my code? [duplicate]为什么 32 位编译器和 64 位编译器会对我的代码产生如此大的影响? [复制]
【发布时间】:2020-03-12 13:20:28
【问题描述】:

请原谅我的英语不好。

我已经写了几行来返回最大值,最小值,所有值的总和,并在输入五个整数时按升序排列所有值。

在编写时,当我需要放入 5 个整数时,我在声明一个 INT 数组时错误地写了“num[4]”。 但是当我使用 TDM-GCC 4.9.2 64 位版本编译时,它没有任何问题。当我意识到并更改为 TDM-GCC 4.9.2 32 位版本时,它没有。

这是我的全部代码;

#include<stdio.h>

int main()
{

    int num[4],i,j,k,a,b,c,m,number,sum=0;
    printf("This program returns max, min, sum of all values, and arranges all values in ascending order when five integers are input.\n");
    printf("Please enter five integers.\n");

    for(i=0;i<5;i++)
    {
        printf("Enter #%d\n",i+1);
        scanf("%d",&num[i]);
    }

    //arrange all values
    for(j=0;j<5;j++)
    {
        for(k=j+1;k<5;k++)
        {
            if(num[j]>num[k])
            {
                number=num[j];
                num[j]=num[k];
                num[k]=number;
            }
        }
    }

    //find maximum value 
    int max=num[0];
    for(a=1;a<5;a++)
    {
        if(max<num[a]) 
        {
            max=num[a];
        }
    }   

    //find minimum value
    int min=num[0];
    for(b=1;b<5;b++)
    {
        if(min>num[b])  
        {
            min=num[b];
        }
    }

    //find sum of all values
    for(c=0;c<5;c++)
    {
        sum=sum+num[c]; 
    } 

    printf("Max Value : %d\n",max);//print max
    printf("Min Value : %d\n",min);//print min 
    printf("Sum : %d\n",sum); //print sum

    printf("In ascending order : "); //print all values in ascending order
    for(m=0;m<5;m++)
    {
        printf("%d ",num[m]);
    }
}

我是 C 和各种编程的新手,不知道如何搜索这些问题。我知道我在这里这样问的方式很不恰当,我真诚地向那些被这类提问帖子激怒的人道歉。但这是我最好的尝试,所以请不要责怪,但我愿意接受任何建议或提示。

谢谢。

【问题讨论】:

  • 你真正的问题是“为什么这个错误出现在 32 位编译器而不是 64 位编译器”?
  • 你有什么意见?程序是否会因任何输入而失败?
  • 通常我会说 64 位和 32 位编译之间的区别在于使用的指令集。但在这种情况下,它可能也会影响编译器以 32 / 64 位块分配内存的方式。使用 32 位编译,事情可能会更紧密地打包在一起,没有足够的备用(已分配但未使用)来容纳 num 中的额外项目,使用 64 位编译会有更多的松弛,额外的数据恰好下降进入一个未使用的区域并且不会导致异常或错误。

标签: c arrays 32bit-64bit


【解决方案1】:

在堆栈上分配时,针对 64 位(可能还有 Clang)的 GCC 会将堆栈分配对齐到 8 个字节。

对于 32 位目标,它只会使用 4 字节的填充。

因此,当您将程序编译为 64 位时,额外使用了四个字节来填充堆栈。这就是为什么当您访问最后一个整数时,它没有出现段错误。

要查看实际情况,我们将创建一个测试文件。

void test_func() {
    int n[4];
    int b = 11;
    for (int i = 0; i < 4; i++) {
      n[i] = b;
    }
}

我们将为 32 位和 64 位编译它。

gcc -g -c -m64 test.c -o test_64.o
gcc -g -c -m32 test.c -o test_32.o

现在我们将为每个打印反汇编。

objdump -S test_64.o >test_64_dis.txt
objdump -S test_32.o >test_32_dis.txt

这是 64 位版本的内容。

test_64.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <func>:
void func() {
   0:   f3 0f 1e fa             endbr64 
   4:   55                      push   %rbp
   5:   48 89 e5                mov    %rsp,%rbp
   8:   48 83 ec 30             sub    $0x30,%rsp
   c:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
  13:   00 00 
  15:   48 89 45 f8             mov    %rax,-0x8(%rbp)
  19:   31 c0                   xor    %eax,%eax
    int n[4];
    int b = 11;
  1b:   c7 45 dc 0b 00 00 00    movl   $0xb,-0x24(%rbp)
    for (int i = 0; i < 4; i++) {
  22:   c7 45 d8 00 00 00 00    movl   $0x0,-0x28(%rbp)
  29:   eb 10                   jmp    3b <func+0x3b>
        n[i] = b;
  2b:   8b 45 d8                mov    -0x28(%rbp),%eax
  2e:   48 98                   cltq   
  30:   8b 55 dc                mov    -0x24(%rbp),%edx
  33:   89 54 85 e0             mov    %edx,-0x20(%rbp,%rax,4)
    for (int i = 0; i < 4; i++) {
  37:   83 45 d8 01             addl   $0x1,-0x28(%rbp)
  3b:   83 7d d8 03             cmpl   $0x3,-0x28(%rbp)
  3f:   7e ea                   jle    2b <func+0x2b>
    }
}
  41:   90                      nop
  42:   48 8b 45 f8             mov    -0x8(%rbp),%rax
  46:   64 48 33 04 25 28 00    xor    %fs:0x28,%rax
  4d:   00 00 
  4f:   74 05                   je     56 <func+0x56>
  51:   e8 00 00 00 00          callq  56 <func+0x56>
  56:   c9                      leaveq 
  57:   c3                      retq   

这里是 32 位版本。

test_32.o:     file format elf32-i386


Disassembly of section .text:

00000000 <func>:
void func() {
   0:   f3 0f 1e fb             endbr32 
   4:   55                      push   %ebp
   5:   89 e5                   mov    %esp,%ebp
   7:   83 ec 28                sub    $0x28,%esp
   a:   e8 fc ff ff ff          call   b <func+0xb>
   f:   05 01 00 00 00          add    $0x1,%eax
  14:   65 a1 14 00 00 00       mov    %gs:0x14,%eax
  1a:   89 45 f4                mov    %eax,-0xc(%ebp)
  1d:   31 c0                   xor    %eax,%eax
    int n[4];
    int b = 11;
  1f:   c7 45 e0 0b 00 00 00    movl   $0xb,-0x20(%ebp)
    for (int i = 0; i < 4; i++) {
  26:   c7 45 dc 00 00 00 00    movl   $0x0,-0x24(%ebp)
  2d:   eb 0e                   jmp    3d <func+0x3d>
        n[i] = b;
  2f:   8b 45 dc                mov    -0x24(%ebp),%eax
  32:   8b 55 e0                mov    -0x20(%ebp),%edx
  35:   89 54 85 e4             mov    %edx,-0x1c(%ebp,%eax,4)
    for (int i = 0; i < 4; i++) {
  39:   83 45 dc 01             addl   $0x1,-0x24(%ebp)
  3d:   83 7d dc 03             cmpl   $0x3,-0x24(%ebp)
  41:   7e ec                   jle    2f <func+0x2f>
    }
}
  43:   90                      nop
  44:   8b 45 f4                mov    -0xc(%ebp),%eax
  47:   65 33 05 14 00 00 00    xor    %gs:0x14,%eax
  4e:   74 05                   je     55 <func+0x55>
  50:   e8 fc ff ff ff          call   51 <func+0x51>
  55:   c9                      leave  
  56:   c3                      ret    

Disassembly of section .text.__x86.get_pc_thunk.ax:

00000000 <__x86.get_pc_thunk.ax>:
   0:   8b 04 24                mov    (%esp),%eax
   3:   c3                      ret    

你可以看到编译器分别生成了 24 个字节和 20 个字节,如果你在变量声明后面看的话。

关于您要求的建议/提示,一个好的起点是启用所有编译器警告并将它们视为错误。在 GCC 和 Clang 中,您将使用 -Wall -Wextra -Werror -Wfatal-errors

不过,如果您使用的是 MSVC 编译器,我不建议您这样做,因为它通常会发出有关与它一起分发的头文件的声明的警告。

【讨论】:

    【解决方案2】:

    通过分析生成的程序集,其他答案涵盖了他可能实际发生的情况,但真正相关的解释是:索引超出数组边界是 C 中的未定义行为。 这就是故事的结尾.

    UB 意味着,代码被 C 标准“允许”做任何事情。每次运行时它都可以做不同的事情。它可以做你想做的事而不会产生不良影响。它可能会做你想做的事,但是完全不相关的事情会以一种有趣的方式表现出来。编译器、操作系统,甚至月相都会产生影响。或者不。

    考虑 C 级别的未定义行为实际发生的情况通常没有用处。您当然可以生成特定编译的汇编输出,并检查它的作用,但这是该编译的结果。新的编译可能会改变一些事情(即使您只是在不同的时间进行新的构建,因为__TIME__ 宏的值取决于时间......)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-22
      • 2023-03-22
      • 2013-07-03
      • 2018-08-04
      • 2014-08-15
      • 2014-01-12
      相关资源
      最近更新 更多