[英]printf and memcpy linkage to standard C library
It is my understanding that if I call printf
in a program, by default (if the program isn't statically compiled) it makes a call to printf
in the standard C library. 据我了解,如果我在程序中调用printf
,则默认情况下(如果程序不是静态编译的)会在标准C库中调用printf
。 However, if I were to call say memcpy
, I'd hope the code would be inlined, as a function call is very expensive if memcpy
is only copying a few bytes. 但是,如果我要说memcpy
,我希望代码可以内联,因为如果memcpy
仅复制几个字节,函数调用将非常昂贵。 If you're inlining sometimes and calling out others, the behaviour of your program after a libc upgrade is implementation dependent. 如果您有时内联并调出其他人,则libc升级后程序的行为取决于实现。
What actually occurs in both of these cases and generally? 在这两种情况下以及通常情况下,实际发生了什么?
An implementation is allowed by the C standard to behave "as if" the actual standard library function were called. C标准允许实现以“好像”调用了实际的标准库函数的方式运行。 This is indeed a common optimization: small memcpy
calls can be unrolled/inlined, and much more. 这确实是一个常见的优化:小型memcpy
调用可以展开/内联,甚至更多。
You're right that in some cases you could upgrade your libc
and not see any change in function calls which were optimized out. 没错,在某些情况下,您可以升级libc
,而看不到已优化的函数调用中的任何更改。
You can always attempt to drive the compiler behavior. 您始终可以尝试驱动编译器行为。 For instance, with gcc
: 例如,使用gcc
:
gcc -fno-inline -fno-builtin-inline -fno-inline-functions -fno-builtin...
You should check the different results with nm
or directly the interrupt calls in the assembly source code. 您应该使用nm
检查不同的结果,或者直接在汇编源代码中检查中断调用。
First of all the function is never truly "inlined" - that applies to functions that you've written that are visible in the same compilation unit. 首先,该函数永远不会真正地“内联”-适用于您编写的在同一编译单元中可见的函数。
If you're inlining sometimes and calling out others, the behaviour of your program after a libc upgrade is implementation dependent. 如果您有时内联并调出其他人,则libc升级后程序的行为取决于实现。
This is not the case. 不是这种情况。 The memcpy
might be "inlined" at compile time . 该memcpy
可能在编译时 “内联”。 Once compiled, your libc version makes no difference. 编译后,您的libc版本没有任何区别。
In GCC, memcpy
is recognized as a builtin . 在GCC中, memcpy
被认为是内置函数。 That means if GCC decides it, the call to memcpy
will be replaced with a suitable implementation. 这意味着,如果GCC做出决定,则对memcpy
的调用将替换为合适的实现。 On x86, this will usually be a rep movsb
or similar instruction - depending on the size of the copy, and if it is of a constant size or not. 在x86上,这通常是rep movsb
或类似的指令-取决于副本的大小,以及大小是否恒定。
It's going to depend on a lot of things, here's how you can find out. 这将取决于很多事情,这是您可以找到的方法。 GNU Binutils comes with a utility objdump
that gives all sorts of details on what's in a binary. GNU Binutils带有一个实用程序objdump
,它提供了有关二进制文件中内容的各种详细信息。
On my system (an ARM Chromebook), compiling test.c: 在我的系统(ARM Chromebook)上,编译test.c:
#include <stdio.h>
int main(void) {
printf("Hello, world!\n");
}
with gcc test.c -o test
and then running objdump -R test
gives 与gcc test.c -o test
,然后运行objdump -R test
给出
test: file format elf32-littlearm
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
000105e4 R_ARM_GLOB_DAT __gmon_start__
000105d4 R_ARM_JUMP_SLOT puts
000105d8 R_ARM_JUMP_SLOT __libc_start_main
000105dc R_ARM_JUMP_SLOT __gmon_start__
000105e0 R_ARM_JUMP_SLOT abort
These are the dynamic relocation entries that are in the file, all the stuff that will be linked in from libraries external to the binary. 这些是文件中的动态重定位条目,所有东西都将从二进制外部的库中链接进来。 Here it seems that the printf
has been entirely optimized out, since it is only giving a constant string, and thus puts
is sufficient. 在这里,似乎printf
已被完全优化,因为它只给出一个恒定的字符串,因此puts
就足够了。 If we modify this to 如果我们将其修改为
printf("Hello world #%d\n", 1);
then we get the expected 然后我们得到了期望
000105e0 R_ARM_JUMP_SLOT printf
To get memcpy
to be explicitly linked to, we have to prevent gcc
from using its own builtin version with -fno-buildin-memcpy
. 为了使memcpy
与之显式链接,我们必须防止gcc
将其内置版本与-fno-buildin-memcpy
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.