c - 以下程序集对以下 .c 文件做了什么

标签 c assembly compiler-construction sparc

我写了下面的代码,你能解释一下程序集在这里告诉我什么吗?

typedef struct
{
    int abcd[5];
} hh;

void main()
{
    printf("%d", ((hh*)0)+1);
}  

程序集:

        .file   "aa.c"
        .section        ".rodata"
        .align 8
.LLC0:

        .asciz  "%d\n"
        .section        ".text"
        .align 4
        .global main
        .type   main, #function
        .proc   020
main:

        save    %sp, -112, %sp
        sethi   %hi(.LLC0), %g1
        or      %g1, %lo(.LLC0), %o0
        mov     20, %o1
        call    printf, 0
         nop
        return  %i7+8
         nop
        .size   main, .-main
        .ident  "GCC: (GNU) 4.2.1"

最佳答案

哇哦,SPARC 汇编语言,我已经没见过了。

我想我们逐行进行?我将跳过一些无趣的样板文件。

        .section        ".rodata"
        .align 8
.LLC0:
        .asciz  "%d\n"

这是您在 printf 中使用的字符串常量(很明显,我知道!)需要注意的重要事项是它位于 .rodata 部分(部分是最终可执行镜像的分区;这个分区用于“只读数据”,实际上在运行时是不可变的)并且它被赋予了标签 .LLC0。以点开头的标签是目标文件私有(private)的。稍后,编译器在要加载字符串常量的地址时将引用该标签。

        .section        ".text"
        .align 4
        .global main
        .type   main, #function
        .proc   020
main:

.text 是实际机器代码的部分。这是用于定义名为 main 的全局函数的样板文件头,它在汇编级别与任何其他函数没有区别(在 C 中——在 C++ 中不一定如此)。我不记得 .proc 020 做了什么。

        save    %sp, -112, %sp

保存之前的寄存器窗口,向下调整堆栈指针。如果你不知道什么是寄存器窗口,你需要阅读架构手册:http://sparc.org/wp-content/uploads/2014/01/v8.pdf.gz . (V8 是 SPARC 的最后一个 32 位迭代,V9 是第一个 64 位迭代。这看起来是 32 位代码。)

        sethi   %hi(.LLC0), %g1
        or      %g1, %lo(.LLC0), %o0

这两条指令序列的实际效果是将地址 .LLC0(这是您的字符串常量)加载到寄存器 %o0 中,这是第一个 传出参数寄存器。 (此函数的参数 传入参数寄存器中。)

        mov     20, %o1

将立即数 100 加载到 %o1,即第二个传出参数寄存器。这是由 ((foo *)0)+1 计算的值。它是 20,因为您的 struct foo 有 20 个字节长(五个 4 字节的 int)并且您要求从地址零开始的数组中的第二个。

顺便说一下,只有在基指针的地址处实际上有一个足够大的数组时,计算指针的偏移量才在 C 中得到明确定义; ((foo *)0) 是一个空指针,所以那里没有数组,所以表达式 ((foo *)0)+1 在技术上没有定义行为。针对托管 SPARC 的 GCC 4.2.1 碰巧将其解释为“假装在地址零处有一个任意大的 foo 数组并计算数组成员 1 的预期偏移量”,但其他 (尤其是较新的)编译器可能会做一些完全不同的事情。

        call    printf, 0
        nop

调用 printf。我不记得零是做什么用的。 call 指令有一个延迟槽(再次阅读体系结构手册),其中填充了一条不执行指令,nop

        return  %i7+8
        nop

跳转到寄存器%i7加八的地址。这具有从当前函数返回的效果。

return 还有一个延迟槽,用另一个nop 填充。在这个延迟槽中应该有一个restore指令,匹配函数顶部的save,这样main的调用者取回其注册窗口。我不知道为什么它不在那里。评论中的讨论讨论了 main 可能不需要弹出注册窗口,和/或您已将 main 声明为 void main() (它不能保证与任何 C 实现一起工作,除非它的文档特别说明,并且 总是 糟糕的风格)...但是按下而不是弹出寄存器窗口是一件很麻烦的事情一个 SPARC,我认为这两个解释都不令人信服。我什至可以称之为编译器错误。

关于c - 以下程序集对以下 .c 文件做了什么,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9287986/

相关文章:

c - freopen 导致段错误

c - 堆栈:低内存与高内存地址的实际位置?

assembly - MASM32循环

c# - 当参数类型不明确时,编译器如何选择调用哪个方法?

c - 使用 zlib 使用 zpipe.c 示例创建 gzip 文件

c - 用 C 编写一个二维数组表。我将如何着手这样做。关于整洁度(例如分割线)

assembly - 如何使用GDB打印复杂寻址模式引用的内存?

linux - 是否可以链接 16 位代码和 32 位代码?

compiler-construction - 为什么需要虚拟机?

C++ 模板 : Convincing self against code bloat