Если я выясню, как компилятор проанализировал его, я обновлю это, но, по крайней мере, не нужно догадываться, как он компилируется:
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
Поскольку исполняемые файлы Linux обычно основаны на 0x8048000, адрес аргумента для printf имеет смещение 0x00004a0 от начала двоичного файла:
xxd /tmp/hello | grep 00004a0
00004a0: 4865 6c6c 6f2c 2077 6f72 6c64 210a 0000 Hello, world!...
Итак, адрес строки передается, и printf вызывается с этим одним аргументом. Ничего волшебного на этом уровне, так что все забавные вещи были сделаны gcc.