【问题标题】:Where are static values stored in assembly汇编中存储的静态值在哪里
【发布时间】:2019-07-05 15:33:16
【问题描述】:

这是一个简单的 C 代码

#include <stdio.h>

int a = 5;

static int b = 20;

int main(){

 int c = 30;

 return 0;
}

编译后没有优化:

    .section    __TEXT,__text,regular,pure_instructions
    .macosx_version_min 10, 13
    .globl  _main                   ## -- Begin function main
    .p2align    4, 0x90
_main:                                  ## @main
    .cfi_startproc
## %bb.0:
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    xorl    %eax, %eax
    movl    $0, -4(%rbp)
    movl    $30, -8(%rbp)
    popq    %rbp
    retq
    .cfi_endproc
                                        ## -- End function
    .section    __DATA,__data
    .globl  _a                      ## @a
    .p2align    2
_a:
    .long   5                       ## 0x5



我的问题是static int b = 20; 在上面的程序集中在哪里? 我知道它们应该在内存的全局部分,但我在编译版本中找不到它。

【问题讨论】:

  • 可能无处可去?编译器可能已将其删除,因为您没有使用它。
  • “未启用优化”并不意味着“不对我的代码进行任何转换”。只需来自mainreturn b,您就会看到它出现。
  • 如果编译器不接触代码,它将输出 C 代码而不是汇编代码,它不会比 cat 更有用。无论如何,显然即使-O0 也做了一些 优化,这些优化对于编译器来说非常简单明了。
  • @Joe 编译成数字 20 对应的汇编代码是什么,没有以任何方式使用?
  • @DavidSchwartz:它有 _a: .long 5 用于未使用的全局变量。 OP 正在查看编译器的完整汇编语言输出,而不仅仅是 机器代码助记符 + 操作数。如果 asm 源中存在数据,也必须将数据组装到(通常是其他部分)输出文件中。

标签: c assembly


【解决方案1】:

如果static 变量没有被编译器优化,它将进入进程的默认数据部分。

在汇编中,通常可以由程序员在指定用于描述数据部分的文件部分中控制。

C 标准在 § 6.2.4 第 3 段中说:

其标识符被声明为 ... 且存储类说明符为 static 的对象具有静态存储持续时间。它的生命周期是程序的整个执行过程,它的存储值只在程序启动之前初始化一次。

使用以下代码:

static int a = 100;

int foo()
{
    return (a / 2);
}

看看_a 符号在MSVC_DATA 段中是如何出现的,GCC 的第 27-30 行和Clang 的第 28-30 行。

【讨论】:

    【解决方案2】:

    您的代码没有使用b,它是文件范围的,因此其他文件中的任何内容都不能使用它。 GCC 不会费心为它发出定义。

    回答标题问题:
    带有非零初始化器的非const 静态/全局变量(即静态存储类)变量将进入.section .data,而不是.bss(零初始化可变)或.rdata(Windows) /.rodata (Linux) 用于非零只读数据。


    gcc 没有完全脑死模式,可以天真地转写为 asm。 请参阅 Disable all optimization options in GCC - GCC 始终必须通过其内部表示进行转换。

    即使在-O0 处,GCC 也总是会忽略未使用的内容。 可能有一种方法可以禁用它,这与 gcc 甚至在 -O0 上所做的其他一些转换不同。

    gcc 和 clang -O0 将每个语句编译成一个单独的 asm 块,用于存储/重新加载所有内容 (for consistent debugging),但在该块中 gcc 仍然应用其标准转换,例如 (x+y) &lt; x 变为 y&lt;0 用于签名x 和 y with gcc8 and newerx / 10 转换为高半部分的乘法 + 移位。 (Why does GCC use multiplication by a strange number in implementing integer division?)。

    if(false) 中的代码即使在-O0 也被 gcc 删除,因此您不能在 GDB 中使用jump

    有些人关心调试版本的运行时性能,尤其是实时软件(如游戏或操作系统)的开发人员,如果运行速度太慢,则无法正确测试。 (游戏中的人机交互,或者操作系统中的设备驱动程序。)


    其他一些编译器在 -O0 上更脑残,所以你经常会看到看起来更像源表达式的 asm。我想我已经看到没有优化的 MSVC 发出指令,将 mov-immediate 放入寄存器,然后 cmp reg,imm,即在运行时执行仅依赖于立即数的分支,因此可以在编译时简单地计算那个表情。

    当然还有真正的非优化编译器,其全部目标只是用固定模式进行音译。例如,我认为Tiny C Compiler 几乎是一次性的,并且在执行过程中会发出 asm(或机器代码)。请参阅 Tiny C Compiler's generated code emits extra (unnecessary?) NOPs and JMPs 显示它是多么简单:它总是在函数序言中发出 sub esp, imm32 ,并且只有在知道函数需要多少堆栈后才回来填充函数末尾的立即数。即使答案是零,也无法将其删除并收紧代码。


    无论如何,查看优化的 asm 通常会更有趣。编写接受 args 并返回值的函数,这样您就可以看到 asm 中有趣的部分,而无需大量样板和存储/重新加载噪音。 How to remove "noise" from GCC/clang assembly output?

    【讨论】:

    • 从技术上讲,MSVC 的/O0 与 GCC 和 Clang 相同。它旨在优化代码以进行调试,因此您可以在 C 代码的各个行上设置断点。它不会省略分支,但会进行基本的编译时算术折叠。例如,参见this code。它知道条件总是计算为真,因此它实际上并不进行常量比较,但它确实发出执行测试和分支的代码,因此您可以在if 上设置断点。而且它永远不会忽略“死”代码。
    【解决方案3】:

    静态变量不存储在内存中。它们只会在使用时出现 例如

    静态int b = 20; c = c + b;

    会编译

    加 c, '20'

    【讨论】:

    • 你在想static const。如果没有const,是的,如果文件中没有任何内容写入变量,则可以进行此优化,但在一般情况下并非如此,并且 gcc 在未启用优化的情况下不会这样做:godbolt.org/z/LR5Uw7。注意mov edx, DWORD PTR b[rip]。此外,'20' 是一个 2 字节字符文字,其 ASCII 代码分别为 '2''0'。即0x3032,因为 x86 是 little-endian。
    【解决方案4】:

    整个问题有点不准确......(重读它,您实际上对“在上面的程序集中”非常具体......哦,那么答案是“无处”......我的其余答案是对于未发布的问题,但希望能解释为什么“无处”是您问题的答案)。

    您有 C 源代码,然后将一些程序集显示为编译器输出(但您没有指定编译器),然后您询问有关程序集...

    C 是在“C 抽象机”上定义的,而您正在查看此类抽象机的特定 x86-64 实现

    虽然该实现确实有一些静态变量通常结束的规则,但它完全取决于编译器 - 它希望如何实现它们。

    在纯汇编中(如手写,或从 CPU 的角度来看)没有“静态值”之类的东西。您只有寄存器、内存和外围设备。

    所以在汇编(机器代码)中,您可以使用某些寄存器或内存的某些部分作为静态变量。哪个更适合您的需求(没有硬性规则会强制您以任何特定方式执行此操作,除非您必须在目标 CPU 的有效机器代码中表达您的想法,但这通常意味着有数十亿种可能性,甚至当将自己限制在“合理”的范围内,它仍然更倾向于数十种可能的方式,而不仅仅是单一的)。

    您甚至可以(在 x86-64 中)创建一个有点复杂的方案,如何将值保持为代码状态(“部分内存”是机器代码占用的内存),即它不会直接写入在内存中作为一个值,但代码将遵循某些代码路径(从许多可能)获得正确的最终结果,即在代码本身中编码值。例如,图灵完备的方法是如何仅使用 mov 指令将 C 源代码编译为 x86-64 机器代码,这可能不会将内存用于静态变量(不确定,是否添加 .data 部分或通过也将它编译成mov 代码,但从它的存在来看,.data 理论上是如何避免的应该是很明显的)。

    因此,您要么询问具有特定编译时间选项的特定 C 编译器如何实现静态值(根据所使用的源和选项,可能会有一些变体)...

    ...或者如果您真的在问“静态值存储在程序集中的什么位置”,那么答案是“只要您的机器代码有效且正确,就可以在任何地方” ,因为整个“静态值”概念比 CPU 运行的级别更高,所以它就像对特定机器代码目的的解释“那是静态值”,但 CPU 中没有特定的指令/支持来处理它。

    【讨论】:

      猜你喜欢
      • 2021-03-18
      • 2023-04-01
      • 1970-01-01
      • 2021-08-17
      • 1970-01-01
      • 1970-01-01
      • 2012-01-13
      • 2015-11-03
      相关资源
      最近更新 更多