如何知道汇编代码是使用RAM?

时间:2018-04-13 01:22:24

标签: assembly

我对装配很新,这是一个基本问题。

我刚刚听说过使用zero bytes of RAM的概念。

我已经通过

编译了一个C ++代码
g++ -O3 main.cpp -S -o main3.s

main.cpp (source)

#include <iostream>
using namespace std;

int main()
{
    int low=10, high=100, i, flag;

    cout << "Prime numbers between " << low << " and " << high << " are: ";

    while (low < high)
    {
        flag = 0;

        for(i = 2; i <= low/2; ++i)
        {
            if(low % i == 0)
            {
                flag = 1;
                break;
            }
        }

        if (flag == 0)
            cout << low << " ";

        ++low;
    }

    return 0;
}

结果如下:

main3.s

    .file   "main.cpp"
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "Prime numbers between "
.LC1:
    .string " and "
.LC2:
    .string " are: "
.LC3:
    .string " "
    .section    .text.startup,"ax",@progbits
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB1561:
    .cfi_startproc
    pushq   %rbx
    .cfi_def_cfa_offset 16
    .cfi_offset 3, -16
    movl    $22, %edx
    movl    $.LC0, %esi
    movl    $_ZSt4cout, %edi
    call    _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
    movl    $10, %esi
    movl    $_ZSt4cout, %edi
    call    _ZNSolsEi
    movl    $5, %edx
    movq    %rax, %rbx
    movl    $.LC1, %esi
    movq    %rax, %rdi
    call    _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
    movq    %rbx, %rdi
    movl    $100, %esi
    movl    $10, %ebx
    call    _ZNSolsEi
    movl    $.LC2, %esi
    movq    %rax, %rdi
    call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    .p2align 4,,10
    .p2align 3
.L6:
    movl    %ebx, %esi
    sarl    %esi
    testb   $1, %bl
    je  .L2
    movl    $2, %ecx
    jmp .L3
    .p2align 4,,10
    .p2align 3
.L14:
    movl    %ebx, %eax
    cltd
    idivl   %ecx
    testl   %edx, %edx
    je  .L2
.L3:
    addl    $1, %ecx
    cmpl    %esi, %ecx
    jle .L14
    movl    %ebx, %esi
    movl    $_ZSt4cout, %edi
    call    _ZNSolsEi
    movl    $1, %edx
    movl    $.LC3, %esi
    movq    %rax, %rdi
    call    _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
.L2:
    addl    $1, %ebx
    cmpl    $100, %ebx
    jne .L6
    xorl    %eax, %eax
    popq    %rbx
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc
.LFE1561:
    .size   main, .-main
    .p2align 4,,15
    .type   _GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB2045:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $_ZStL8__ioinit, %edi
    call    _ZNSt8ios_base4InitC1Ev
    movl    $__dso_handle, %edx
    movl    $_ZStL8__ioinit, %esi
    movl    $_ZNSt8ios_base4InitD1Ev, %edi
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    jmp __cxa_atexit
    .cfi_endproc
.LFE2045:
    .size   _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
    .section    .init_array,"aw"
    .align 8
    .quad   _GLOBAL__sub_I_main
    .local  _ZStL8__ioinit
    .comm   _ZStL8__ioinit,1,1
    .hidden __dso_handle
    .ident  "GCC: (Ubuntu 7.2.0-1ubuntu1~16.04) 7.2.0"
    .section    .note.GNU-stack,"",@progbits

这是一个基本程序,可以将所有变量存储到CPU寄存器中。因此,我猜它不使用RAM。我想知道检查汇编代码是否使用RAM的标准是什么?

2 个答案:

答案 0 :(得分:4)

在您链接的剪辑中,Jason Turner只是说C局部变量都适合寄存器,因此编译器不必花费额外的指令spilling/reloading them

它使用RAM存储代码和数据,它不使用任何堆栈内存来存储局部变量。即 RAM 的零字节用于局部变量 ,当然不是零字节总数。他甚至说游戏编译成1005个字节(代码+数据)。

通过注意到堆栈缺少装载/存储来读取asm时检测到此情况,例如在x86-64上使用RSP(或RBP,如果用作帧指针)的寻址模式。

对于不是很大的功能,这是完全正常的。内联函数调用是实现它的关键,因为编译器通常必须有内存&#34;同步&#34; (在反映非抽象函数时反映C抽象机器的正确值)。

int foo(int num) {
    int tmp = num * num;
    return tmp;
}

在注册表中获取num,并在其中保留tmp。 Jason的演讲使用的是Godbolt,所以这里是the same function on Godbolt的链接,由gcc7.3编译,有无优化:

 foo:   # with optimization: all operands are registers
    imul    edi, edi
    mov     eax, edi
    ret

foo:    # without optimization:
    push    rbp
    mov     rbp, rsp                     # make a stack frame with RBP
    mov     DWORD PTR [rbp-20], edi      # spill num to the stack
      # start of code for first C statement
    mov     eax, DWORD PTR [rbp-20]      # reload it
    imul    eax, DWORD PTR [rbp-20]      # and use it from memory again
    mov     DWORD PTR [rbp-4], eax       # spill tmp to the stack
      # end of first C statement

    mov     eax, DWORD PTR [rbp-4]       # load tmp into the return value register, eax)
    pop     rbp
    ret

这并没有保留任何堆栈空间sub rsp, 24,因为它使用RSP下方的红区为本地人提供溢出/重装。

显然,启用优化后,即使编译器在大型复杂功能中耗尽了寄存器并且必须溢出某些内容,您也不会将代码变得很糟糕。 -O0是一种反优化模式,其中每个C语句都获得一个单独的asm块,因此您可以设置断点并修改变量并使代码仍然有效。甚至可以跳转到gdb中的其他源代码行!

Re:x86中有多少个寄存器,如话题中所述:

i386有8个架构整数寄存器。它有一些段寄存器可以滥用以保留额外的值,如果它有一个FPU,则有8个x87 80位FP堆栈寄存器。 Jason对16的猜测听起来很虚伪,但他可能将AL / AH,BL / BH计为单独的8位寄存器,因为你可以独立使用它们。但与EAX不同,因为窄寄存器是完整寄存器的子集。

(并注意partial-register penalties on various modern microarchitectures。在AMD,AL和AH根本不是独立的;使用一个对另一个有假依赖,即在整个EAX / RAX上。  在Pentium P5MMX以及包括Pentium P5MMX在内的CPU上,根本没有部分寄存器处罚,因为没有无序执行或寄存器重命名。)

他声称现代x86-64拥有数百个寄存器也绝对是假的,除非你统计所有的控制寄存器和模型特定的寄存器。但堆栈内存 比那些寄存器快,并且无论如何都不能在其中放置任意值。只有16个架构整数寄存器(其中一个是堆栈指针,所以你可以在一个大函数中使用15个注册表),当你需要更多的变量时,你仍然需要额外的指令来溢出或至少重新加载东西&#34; live& #34;不止于此。

将寄存器重命名为大型物理寄存器池非常棒,essential along with a large ReOrder Buffer for a large out-of-order execution window可以查找指令级并行性。但是,您只能通过为不同的值重用相同的整数寄存器来利用这些寄存器。 (即register renaming avoids write-after-read and write-after-write hazards,使同一个寄存器的两次使用实际上是独立的。)

Haswell有一个用于整数/ GP寄存器的168项物理寄存器文件,以及一个用于重命名FP /向量寄存器的168项向量/ FP寄存器文件。 https://www.realworldtech.com/haswell-cpu/3/。但在架构上,它在x86-64模式下运行时只有16 GP / 16 YMM,在ia-32模式下只有8/8。

答案 1 :(得分:2)

变量不是主内存存储的唯一内容。实际上,当您运行程序时,您的操作系统会为负责运行可执行文件的进程保留一些空间(称为地址空间)。

编译生成的汇编代码存储在一个部分(.text部分)中,数据(不要说).data部分,静态变量初始化为{ {1}}部分中的{1}}等等。例如,字符串通常存储在只读部分(0)中。

所以答案是没有,每个程序在运行时都必须使用内存。