当前位置: 首页 > 知识库问答 >
问题:

关于gcc编译x86_64代码和C代码优化

宇文飞羽
2023-03-14

我编译了以下C代码:

typedef struct {
    long x, y, z;
} Foo;

long Bar(Foo *f, long i)
{
    return f[i].x + f[i].y + f[i].z;
}

使用命令 gcc -S -O3 测试.下面是输出中的 Bar 函数:

    .section    __TEXT,__text,regular,pure_instructions
    .globl  _Bar
    .align  4, 0x90
_Bar:
Leh_func_begin1:
    pushq   %rbp
Ltmp0:
    movq    %rsp, %rbp
Ltmp1:
    leaq    (%rsi,%rsi,2), %rcx
    movq    8(%rdi,%rcx,8), %rax
    addq    (%rdi,%rcx,8), %rax
    addq    16(%rdi,%rcx,8), %rax
    popq    %rbp
    ret
Leh_func_end1:

我有几个关于这个汇编代码的问题:

>

  • 如果函数体中既没有使用rbp也没有使用rbprsp,那么"pushq%rbp","movq%rsp"和"popq%rbp"的目的是什么?
  • 为什么rsirdi自动包含C函数的参数(分别为if)而不从堆栈中读取它们?
  • 我尝试将Foo的大小增加到88字节(11longs),leaq指令变成了imulq。将我的结构设计为“圆形”大小以避免乘法指令(以优化数组访问)是否有意义?leaq指令被替换为:

    imulq   $88, %rsi, %rcx
    
  • 共有3个答案

    澹台镜
    2023-03-14
    3. I tried increasing the size of Foo to 88 bytes (11 longs) and the leaq instruction became an imulq. Would it make sense to design my structs to have "rounder" sizes to avoid the multiply instructions (in order to optimize array access)?
    

    leaq调用(本质上和在本cae中)计算k*a b,其中“k”是1、2、4或8,“a”和“b”是寄存器。如果“a”和“b”相同,它可以用于1、2、3、4、5、8和9长的结构。

    更大的结构,如16 long,可以通过计算“k”和加倍的偏移量来优化,但我不知道这是否是编译器实际会做的;你必须测试。

    孟楷
    2023-03-14
    匿名用户

    1. 如果函数体中没有使用rbp或rsp,那么pushq%rbp、movq%rsp、%rbp和popq%rbp的用途是什么

    使用调试器时跟踪帧。添加< code >-fomit-frame-pointer 进行优化(注意,它应该在< code>-O3上启用,但在我使用的许多< code>gcc版本中它没有启用)。

    陈允晨
    2023-03-14

    >

  • 该函数只是用这些指令构建自己的堆栈框架。它们没有什么特别的。但是,您应该注意,由于这个函数的大小很小,它在代码中使用时可能会内联。但是,编译器总是需要生成函数的“普通”版本。另外,@ouah在他的回答中说。

    这是因为 AMD64 ABI 就是这样指定应将参数传递给函数的方式。

    如果类别是整数,则使用序列为%rdi,%rsi,%rdx,%rcx,%r8和%r9的下一个可用寄存器。

    第 20 页,AMD64 ABI 草案 0.99.5 – 2010 年 9 月 3 日

    这与结构大小没有直接关系,而是函数必须访问的绝对地址。如果结构的大小为24个字节,f是包含结构的数组的地址, i是必须访问数组的索引,则每个结构的字节偏移量为 i*24。在这种情况下,乘以24是通过 lea和SIB寻址的组合实现的。第一条 lea指令简单地计算 i*3,然后每个后续指令使用该 8(%rdi,%rcx,8)16(%rdi,%rcx,8))。如果将结构的大小设为88个字节,那么将lea和任何类型的寻址结合起来,根本无法快速完成这一任务。编译器只是假设一个简单的imull比一系列移位、加法、leas或其他任何方法在计算 时更有效。

  •  类似资料:
    • 代码不编译。我不明白错误是什么,请帮忙) 错误文本:g-Wall-c“main.cpp”(/media/ad/4GB-NTFS/prog/laba2)main。cpp:In函数“int main()”:main。cpp:46:12:错误:调用“Record::Record()”记录r1;^主要的cpp:12:1:注意:候选者:Record::Record(std::\u cxx11::string

    • 问题内容: 现在,我已经在Windows 7上成功安装了Cython,我尝试使用Cython编译一些Cython代码,但是gcc使我的生活变得艰难。 使用gcc编译代码时,会抛出数十个 对 -erros的 未定义引用 ,并且我很确定src是可用的(如安装教程所述,如果缺少此文件,则会抛出 对 -errors的 未定义引用 )。 奇怪的是,使用*或-script可以很好地工作,但是当仍然在模块上工作

    • 我经常遇到这种情况。乍一看,我认为,“这是糟糕的编码;我正在执行一个方法两次,必然会得到相同的结果。”但想到这里,我不得不怀疑编译器是否像我一样聪明,并能得出相同的结论。 编译器的行为是否取决于 方法的内容?假设它看起来像这样(有点类似于我现在的真实代码): 除非对这些对象来自的任何存储进行处理不当的异步更改,否则如果连续运行两次,肯定会返回相同的内容。但是,如果它看起来像这样(为了论证而无意义的

    • 我正在为64位mips机器使用gcc编译器。我注意到生成的一段汇编代码很有趣。下面是详细信息: 通常,bnez将立即跳到0xb0。但在0xb0之后的块中,我确信程序必须使用a1作为参数。但是我们可以看到,在0xb0之后,a1从未出现在块中。 但是a1在0x58中使用,就在bnez(0x54)之后。 那么0x54和0x58指令有可能同时执行吗?超标量处理器通过同时将多条指令分派到处理器上的冗余功能单

    • 我想编译并运行一个简单的Hello World程序,该程序声明并调用Java中的本机print方法(用C定义)。 java下载 CPP. cpp 在命令提示符中,我运行以下命令: javac HelloCPP. java javah-jni HelloCPP 86_64-w64-ming w32-g-c-I"C:\Java\jdk1.8.0_171\include"-I"C:\Java\jdk1.

    • 1.1. 代码编译 1.1.1. Openwrt编译 1.1.2. Kernel编译 1.1.3. Uboot编译 1.1.4. VSP编译 1.1. 代码编译 1.1.1. Openwrt编译 作为Kamino18 YODAOS的整体编译环境,使用openwrt可以编译出系统正常运行所需的主要image如下: 镜像名字 镜像运行位置 镜像说明 镜像生成位置 mcu.bin MCU The ima