C语言 多个printf()调用与一个长字符串的printf()调用?

zf2sa74q  于 2024-01-06  发布在  其他
关注(0)|答案(5)|浏览(135)

假设我有一行printf()和一个长字符串:

  1. printf( "line 1\n"
  2. "line 2\n"
  3. "line 3\n"
  4. "line 4\n"
  5. "line 5\n"
  6. "line 6\n"
  7. "line 7\n"
  8. "line 8\n"
  9. "line 9\n.. etc");

字符串
与每行有多个printf()相比,这种风格的成本是多少?
如果字符串太长,是否会出现堆栈溢出?

yhqotfr8

yhqotfr81#

与每行有多个printf()相比,这种风格的成本是多少?
多个printf将导致多个函数调用,这是唯一的开销。
如果字符串太长,是否会出现堆栈溢出?
在这种情况下没有堆栈溢出。字符串通常存储在只读内存中,而不是堆栈内存中。当字符串传递给printf时,只有指向其第一个元素的指针被复制到堆栈。
加密器将处理此多行字符串

  1. "line 1\n"
  2. "line 2\n"
  3. "line 3\n"
  4. "line 4\n"
  5. "line 5\n"
  6. "line 6\n"
  7. "line 7\n"
  8. "line 8\n"
  9. "line 9\n.. etc"

字符串
作为单字符串

  1. "line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9\n.. etc"


并且这将被存储在存储器的只读部分中。
但请注意(由pmg在评论中指出)C11标准章节 *5.2.4.1翻译限制 * 指出,
实现应能够翻译和执行至少一个程序,该程序至少包含以下限制中的每一个的一个示例18):
[...]

  • 4095字符串文字(串联后)

[...]

展开查看全部
pobjuy32

pobjuy322#

如果字符串之间没有空格或空格,则C将它们连接起来。

  1. printf( "line 1\n"
  2. "line 2\n"
  3. "line 3\n"
  4. "line 4\n"
  5. "line 5\n"
  6. "line 6\n"
  7. "line 7\n"
  8. "line 8\n"
  9. "line 9\n.. etc");

字符串
从可读性的Angular 来看,一个printf调用无疑比9个printf调用的开销要小。

frebpwbc

frebpwbc3#

如果你只输出常量字符串,printf是一个很慢的函数,因为printf必须扫描每个字符的格式说明符(%)。像puts这样的函数对于长字符串来说要快得多,因为它们基本上只需要将输入字符串memcpy输入到输出I/O缓冲区。
许多现代编译器(GCC,Clang,可能还有其他)都有一个优化,如果输入字符串是一个常量字符串,没有以换行符结尾的格式说明符,则会自动将printf转换为puts。例如,编译以下代码:

  1. printf("line 1\n");
  2. printf("line 2\n");
  3. printf("line 3"); /* no newline */

字符串
生成以下程序集(Clang 703.0.31,cc test.c -O2 -S):

  1. ...
  2. leaq L_str(%rip), %rdi
  3. callq _puts
  4. leaq L_str.3(%rip), %rdi
  5. callq _puts
  6. leaq L_.str.2(%rip), %rdi
  7. xorl %eax, %eax
  8. callq _printf
  9. ...


也就是puts("line 1"); puts("line 2"); printf("line 3");
如果你的长printf字符串不以换行符结尾,那么你的性能可能会比你用换行符结尾的字符串进行一堆printf调用时差得多,仅仅是因为这种优化。为了演示,考虑下面的程序:

  1. #include <stdio.h>
  2. #define S "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
  3. #define L S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S
  4. /* L is a constant string of 4000 'a's */
  5. int main() {
  6. int i;
  7. for(i=0; i<1000000; i++) {
  8. #ifdef SPLIT
  9. printf(L "\n");
  10. printf(S);
  11. #else
  12. printf(L "\n" S);
  13. #endif
  14. }
  15. }


如果没有定义SPLIT(生成一个没有终止换行符的printf),时间如下所示:

  1. [08/11 11:47:23] /tmp$ cc test.c -O2 -o test
  2. [08/11 11:47:28] /tmp$ time ./test > /dev/null
  3. real 0m2.203s
  4. user 0m2.151s
  5. sys 0m0.033s


如果定义了SPLIT(产生两个printf,一个有终止换行符,另一个没有),时间看起来像这样:

  1. [08/11 11:48:05] /tmp$ time ./test > /dev/null
  2. real 0m0.470s
  3. user 0m0.435s
  4. sys 0m0.026s


因此,您可以看到,在本例中,将printf拆分为两部分实际上会产生4倍的加速。当然,这是一个极端的情况,但它说明了printf如何根据输入进行优化。(请注意,使用fwrite甚至更快- 0.197s -所以如果您真的想要速度,您应该考虑使用它!)。
tl;dr:如果你只打印大的常量字符串,完全避免使用printf,使用更快的函数,比如putsfwrite

展开查看全部
ca1c2owp

ca1c2owp4#

一个没有格式修饰符的printf被静默地替换(也就是优化)为一个puts调用。这已经是一个加速。你不想在多次调用printf/puts时失去它。
GCC内置了printf(以及其他),因此它可以在编译时优化调用。
参见:

li9yvcax

li9yvcax5#

每一次额外的printf(或者如果你的编译器以这种方式优化它的话,则是puts)都会导致系统特定的函数调用开销,尽管优化很有可能会以任何方式将它们合并组合在一起。
我还没有看到一个printf实现是一个叶函数,所以预计额外的函数调用开销像vfprintf和它的被调用者。
那么每次写操作你可能会有一些系统调用开销。因为printf使用的是stdout,它是缓冲的,所以这些(真正昂贵的)上下文切换通常是可以避免的.除了上面所有的例子都以新行结束。你的大部分开销可能会在这里。
如果你真的很担心主线程的开销,把这类东西移到一个单独的线程。

相关问题