【问题标题】:Does printf consume stack frame in C?printf 是否消耗 C 中的堆栈帧?
【发布时间】:2017-11-21 10:27:48
【问题描述】:

假设我们创建了一个用户函数void func()func 的堆栈帧是由函数调用分配的,只要它在诸如 ma​​in 之类的其他函数中被调用。

printf 一样吗? printf 是否也消耗堆栈帧?

【问题讨论】:

  • 函数调用是函数调用是函数调用......所有函数调用都需要一个堆栈帧(如果这是实现使用的)。无论是您自己的函数、标准 C 函数、操作系统特定函数还是来自第三方库的函数。
  • C 标准不需要堆栈。并且一些支持堆栈的实现不一定将它用于所有函数调用。
  • 但是对于不同的平台有 C 调用约定,所以这个问题是完全合法的(尽管 OP 应该已经指定了他所要求的平台,可能是 x86_64)
  • @Ctx 还是太宽泛了,因为优化,printf 的实际实现(可能是宏/包装器等)
  • 我的猜测是我们这里有一个误解:一个由被调用函数分配的堆栈帧(用于局部变量等)和在堆栈上移交的参数 被调用函数很接近,但是两个不同的东西。

标签: c stack printf


【解决方案1】:

前提:堆栈帧只是许多 C 实现的一个实现细节(具体来说,它通常在 x86 上使用);它既不是 C 标准需要的东西(C 标准不知道堆栈的任何事情),也不是所有实现都需要的东西(甚至可能取决于编译器标志/优化器的考虑)。

现在,当然库函数最终只是已经编译到标准库中的常规函数​​,所以在这方面没有什么特别的:在使用堆栈帧的实现中,每当执行函数调用时,被调用的函数集增加它的堆栈帧(除非它是内联的或者它完全是微不足道的,但这通常不会发生在像 printf 这样的库函数中)。

不过,没什么好担心的——当函数返回时,新的堆栈帧被丢弃,它的堆栈空间再次可用,所以你不必小心不要调用太多的函数。

【讨论】:

  • NMDV,然而,stack frame 的概念虽然有用,但在 C 规范之外。 stack frame 是一些 C 编译器的实现细节。编译器可以通过其他方式解决此问题。这个答案听起来像 stack frame 是 C 编码的通用属性。
  • @chux:鉴于问题是关于堆栈帧的,我的回答是关于采用这种概念的实现(就像大多数“正常”实现一样)。
  • 我怀疑这就是为什么人们 DV 没有解释。评论askedcomment 提供了一个可能的动机,然后我得到了一个带有争议点的回复。 “printf 是否在 C 中消耗堆栈帧?”可以回答“不一定”并提及可能性。 OT:大多数实现都是嵌入式处理器,通常不使用 SF。
  • @chux:好的,我会补充说明。
【解决方案2】:

函数的堆栈帧由该函数保留;当然,没有其他函数可以知道其他函数的堆栈使用需求。

因此,当您从main() 调用func() 时,func() 中的代码(有时称为“前导码”)将保留该函数所需的堆栈空间。

是的,这适用于所有函数,包括 printf()

【讨论】:

    【解决方案3】:

    这里有一个带有main 的小代码,它调用了一个名为func 的简单函数。

    我们可以使用gcc main.c -S main.s 编译这个c 代码。 main.s 应为汇编输出。

    在程序集中,您可能会看到 CPU 指令:push、pop、leave、enter。这些指令负责栈帧管理。

    如果您使用带有其他选项的 gcc 进行编译,您可能会注意到一些堆栈管理差异。

    #include <stdio.h>
    
    int func(void);
    
    int func(void)
    {
        int i;
    
        for(i=0;i<100;i++)
            printf("%d",i);
    
        return i;
    }
    
    int main(void)
    {
        return func();
    }
    

    GCC 编译器汇编输出

            .file   "main.c"
            .section        .rodata
    .LC0:
            .string "%d"
            .text
            .globl  func
            .type   func, @function
    func:
    .LFB0:
            .cfi_startproc
            pushq   %rbp
            .cfi_def_cfa_offset 16
            .cfi_offset 6, -16
            movq    %rsp, %rbp
            .cfi_def_cfa_register 6
            subq    $16, %rsp
            movl    $0, -4(%rbp)
            jmp     .L2
    .L3:
            movl    -4(%rbp), %eax
            movl    %eax, %esi
            movl    $.LC0, %edi
            movl    $0, %eax
            call    printf
            addl    $1, -4(%rbp)
    .L2:
            cmpl    $99, -4(%rbp)
            jle     .L3
            movl    -4(%rbp), %eax
            leave
            .cfi_def_cfa 7, 8
            ret
            .cfi_endproc
    .LFE0:
            .size   func, .-func
            .globl  main
            .type   main, @function
    main:
    .LFB1:
            .cfi_startproc
            pushq   %rbp
            .cfi_def_cfa_offset 16
            .cfi_offset 6, -16
            movq    %rsp, %rbp
            .cfi_def_cfa_register 6
            call    func
            popq    %rbp
            .cfi_def_cfa 7, 8
            ret
            .cfi_endproc
    .LFE1:
            .size   main, .-main
            .ident  "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"
            .section        .note.GNU-stack,"",@progbits
    

    这里是函数funcobjdump main.o -S)的objdump输出。 main.o 获得者:gcc main.c -o main.o

    000000000040052d <func>:
      40052d:   55                      push   %rbp
      40052e:   48 89 e5                mov    %rsp,%rbp
      400531:   48 83 ec 10             sub    $0x10,%rsp
      400535:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)
      40053c:   eb 18                   jmp    400556 <func+0x29>
      40053e:   8b 45 fc                mov    -0x4(%rbp),%eax
      400541:   89 c6                   mov    %eax,%esi
      400543:   bf f4 05 40 00          mov    $0x4005f4,%edi
      400548:   b8 00 00 00 00          mov    $0x0,%eax
      40054d:   e8 be fe ff ff          callq  400410 <printf@plt>
      400552:   83 45 fc 01             addl   $0x1,-0x4(%rbp)
      400556:   83 7d fc 63             cmpl   $0x63,-0x4(%rbp)
      40055a:   7e e2                   jle    40053e <func+0x11>
      40055c:   8b 45 fc                mov    -0x4(%rbp),%eax
      40055f:   c9                      leaveq 
      400560:   c3                      retq   
    
    0000000000400561 <main>:
      400561:   55                      push   %rbp
      400562:   48 89 e5                mov    %rsp,%rbp
      400565:   e8 c3 ff ff ff          callq  40052d <func>
      40056a:   5d                      pop    %rbp
      40056b:   c3                      retq   
      40056c:   0f 1f 40 00             nopl   0x0(%rax)
    

    【讨论】:

    • 不应该 movl $.LC0, %edimovabsq $.LC0, %rdi (或 movq $.LC0(%rip), %rdi)考虑到您的指针是 64 位(因为 X86_64)。你能澄清一下你正在编译的目标吗?这与问题完全无关,但让我感到疑惑。
    • 这是 Linux 环境中 X86_64 (Intel I3) CPU 的 gcc 编译器的汇编输出,其中外部(和标准)函数被称为加载动态库。
    • 如果您需要了解函数在何处以及如何使用堆栈,您必须查看与func 函数相关的代码。
    • 我在答案中添加了其他信息。此信息是使用 objdump 获得的。我认为应该更清楚。如果您想了解堆栈的使用(也包括在 printf 函数中),您可以使用以下命令编译程序:gcc -static main.c -o main) and then you have to use objdump -S main` 以生成程序集转储。
    • 您的问题的直接答案是 printf 至少在 X86[_64] Linux/Windows 实现中使用堆栈帧。 (我们可以假设这是最广泛的行为)
    【解决方案4】:

    取决于实施。在所有标准中都没有必要调用尾部函数来消耗堆栈帧,但通常实现确实会消耗一个帧。

    说的太对了

    func 的堆栈帧是由函数调用分配的 在其他一些函数中调用,例如 main ONLY in some implementations

    【讨论】:

      猜你喜欢
      • 2016-06-25
      • 1970-01-01
      • 2014-06-10
      • 2013-10-12
      • 2013-11-04
      • 2019-02-11
      • 1970-01-01
      • 2016-01-12
      • 1970-01-01
      相关资源
      最近更新 更多