我通过反汇编一些C代码来学习汇编语言。当我用GDB反汇编这个基本的C代码时:
#include <stdio.h>
void main(void) {
printf("Hello World\n");
}
在汇编代码中,它给出了这一行:
0x08048424 <+25>: call 0x80482e0 <puts@plt>
但是,当我在printf函数中具有整数的代码下面进行反汇编时:
#include <stdio.h>
void main(void) {
int a = 1;
printf("Hello Word %d\n", a);
}
它给出了这一行:
0x0804842e <+35>: call 0x80482e0 <printf@plt>
printf @ plt和puts @ plt有什么区别?
为什么反汇编程序在没有整数参数的情况下无法识别printf函数?
答案 0 :(得分:9)
在GCC中printf
和puts
是内置函数。这意味着编译器完全了解它们的语义。在这种情况下,如果编译器认为它将产生更好(更快和/或更紧凑)的代码,则可以自由地用对另一个函数的等效调用替换对一个函数的调用。
puts
通常是一种更有效的函数,因为它不需要解析和解释格式字符串。
这正是你案件中发生的事情。您第一次拨打printf
并不需要任何printf
特定功能。您提供给printf
的格式字符串很简单:它没有转换说明符。编译器认为,通过对printf
的等效调用,可以更好地为puts
拨打电话。
与此同时,您第二次拨打printf
可以轻松使用printf
格式字符串,即它依赖于printf
特定功能。
(2005年对这一具体问题的一些相当彻底的研究:http://www.ciselant.de/projects/gcc_printf/gcc_printf.html)
答案 1 :(得分:4)
我不知道@plt
部分,但printf
和puts
只是两个不同的标准库函数。 printf
采用格式字符串和零个或多个其他参数,可能是不同类型的参数。 puts
只需一个字符串并打印出来,然后换行。有关更多信息,请参阅任何C参考,或键入
man 3 printf
man 3 puts
假设您使用的是安装了手册页的类Unix系统。 (man printf
没有3
会显示printf
命令;您需要printf
功能。)< / p>
您的编译器能够优化呼叫
printf("Hello, world\n");
相当于:
puts("Hello, world");
因为它知道两个函数的作用,所以它可以确定它们完全相同。
无法优化
printf("Hello Word %d\n", a);
因为a
的值在编译时是未知的,所以它不会打印固定的字符串。 (通过观察a
在初始化后永远不会被修改,它可能会在更高的优化级别上找出它。)
反汇编程序只是向您展示编译器生成的代码。
(顺便提一下,void main(void)
不正确;请使用int main(void)
。)
答案 2 :(得分:2)
puts
和printf
函数似乎具有相同的地址,因为您正在查看存根,而不是真正的函数。这些存根从过程链接表中加载一个地址(@plt
后缀所指的内容),然后调用它。
我反汇编程序,发现它有printf
和puts
的存根:
08048370 <printf@plt>:
8048370: ff 25 04 a0 04 08 jmp *0x804a004
8048376: 68 08 00 00 00 push $0x8
804837b: e9 d0 ff ff ff jmp 8048350 <_init+0x3c>
08048380 <puts@plt>:
8048380: ff 25 08 a0 04 08 jmp *0x804a008
8048386: 68 10 00 00 00 push $0x10
804838b: e9 c0 ff ff ff jmp 8048350 <_init+0x3c>
如您所见,实际函数位于其他位置,并且这些存根仅为程序实际使用的函数生成。如果您的程序只调用一个函数,然后将其从printf
更改为puts
,那么唯一的存根位于同一地址并不令人惊讶。我刚刚反汇编的程序调用{{1}}和printf
,因此两者都有存根,因此它们有不同的地址。