printf@plt 和 puts@plt 的区别

Difference between printf@plt and puts@plt

我一直在通过反汇编一些 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 函数?

在 GCC 中,printfputs 是内置函数。这意味着编译器完全了解它们的语义。在这种情况下,编译器可以自由地用对另一个函数的等效调用替换对一个函数的调用,如果它认为它会产生更好(更快 and/or 更紧凑)的代码。

puts 通常是更高效的函数,因为它不必解析和解释格式字符串。

这正是您的情况。您对 printf 的第一次调用实际上并不需要任何 printf 特定的功能。您提供给 printf 的格式字符串很简单:其中没有转换说明符。编译器认为您对 printf 的第一次调用最好与对 puts.

的等效调用一起使用

与此同时,您对 printf 的第二次调用对 printf 格式字符串进行了非平凡的使用,即它依赖于 printf 特定的功能。

(从 2005 年开始对这个具体问题进行了一些相当深入的研究:http://www.ciselant.de/projects/gcc_printf/gcc_printf.html

我不知道 @plt 部分,但 printfputs 只是两个不同的标准库函数。 printf 采用格式字符串和零个或多个可能不同类型的其他参数。 puts 只接受一个字符串并打印它,后跟一个换行符。查阅任何 C 参考以获取更多信息,或键入

man 3 printf
man 3 puts

假设您使用的是安装了手册页的类 Unix 系统。 (没有 3man printf 将向您显示 printf 命令 ;您需要 printf 函数 .)

您的编译器能够优化调用

printf("Hello, world\n");

相当于:

puts("Hello, world");

因为它知道这两个函数的作用,所以它可以确定它们做的是完全相同的事情。

无法优化

printf("Hello Word %d\n", a);

因为a的值在编译时是未知的,所以不会打印固定的字符串。 (通过观察 a 在其初始化后从未被修改,它可能会在更高的优化级别上解决这个问题)。

反汇编程序只是向您展示编译器生成的代码。

(顺便说一下,void main(void) 不正确;请使用 int main(void)。)

putsprintf 函数似乎具有相同的地址,因为您正在查看 stubs,而不是真正的函数。这些存根从过程 link table(@plt 后缀指的是什么)加载一个地址,然后调用它。

我正在反汇编一个程序,发现它有 printfputs 的存根:

08048370 <printf@plt>:
 8048370:       ff 25 04 a0 04 08       jmp    *0x804a004
 8048376:       68 08 00 00 00          push   [=10=]x8
 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   [=10=]x10
 804838b:       e9 c0 ff ff ff          jmp    8048350 <_init+0x3c>

如您所见,真正的函数在其他地方,这些存根只是为您的程序实际使用的函数生成的。如果您的程序只调用一个函数,然后将其从 printf 更改为 puts,那么唯一的存根位于同一地址就不足为奇了。我刚刚反汇编的程序调用了 printfputs,因此两者都有存根,因此它们具有不同的地址。