我对装配很新,这是一个基本问题。
我刚刚听说过使用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的标准是什么?
答案 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
)中。
所以答案是没有,每个程序在运行时都必须使用内存。