C代码:这些甚至如何工作?

Rev*_*nzo 22 c puzzle

我刚看到这里

#include <stdio.h>

int main(int argc, char *argv[printf("Hello, world!\n")]) {}
Run Code Online (Sandbox Code Playgroud)

这样做是打印"Hello World!"

但是这里到底发生了什么?

我能猜到的最好的是它被编译并抛出执行堆栈的顶部,但语法对我来说看起来不合法......

cas*_*nca 21

该代码使用了C99的可变长度数组功能,该功能允许您声明仅在运行时知道其大小的数组.printf返回一个等于实际打印的字符数的整数,因此代码打印出"Hello,world!" 首先使用返回值作为大小argv.该main函数本身并没有什么.printf自身的实际调用可能会进入编译器生成的启动代码,而编译器又会调用main.

编辑:我刚检查了生成的代码的反汇编,gcc看起来调用在其他任何代码之前printf进入main自身.


jco*_*ctx 5

如果我弄清楚编译器如何解析它,我会更新它,但至少不需要猜测它是如何编译的:


objdump --disassemble /tmp/hello (edited):

080483c4 <main>:
 80483c4:       55                      push   %ebp
 80483c5:       89 e5                   mov    %esp,%ebp
 80483c7:       83 e4 f0                and    $0xfffffff0,%esp
 80483ca:       83 ec 10                sub    $0x10,%esp
 80483cd:       b8 a0 84 04 08          mov    $0x80484a0,%eax
 80483d2:       89 04 24                mov    %eax,(%esp)
 80483d5:       e8 22 ff ff ff          call   80482fc <printf@plt>
 80483da:       c9                      leave  
 80483db:       c3                      ret    
 80483dc:       90                      nop
 80483dd:       90                      nop
 80483de:       90                      nop
 80483df:       90                      nop
Run Code Online (Sandbox Code Playgroud)

由于Linux可执行文件通常位于0x8048000,因此printf参数的地址与二进制文件的起始位置的偏移量为0x00004a0:


xxd /tmp/hello | grep 00004a0

00004a0: 4865 6c6c 6f2c 2077 6f72 6c64 210a 0000  Hello, world!...
Run Code Online (Sandbox Code Playgroud)

因此,推送字符串的地址,并使用该arg调用printf.没有什么神奇的,所以有趣的东西都是由gcc完成的.