如果一个 C 函数被调用两次,它会创建一个在函数中声明的变量两次吗?

If a C function is called twice will it create a variable declared in the function twice?

我有一个用 C 编写的函数,它由一个像这样的指针变量组成

#include<stdio.h>
void print()
{
    char *hello="hello world";
    fprintf(stdout,"%s",hello);
}

void main()
{
    print();
    print();
}

如果我调用 print() 函数两次,它会为 hello 变量分配两次内存吗?

if i call the print() function twice, will it allocate the memory for the hello variable twice?

不,它是一个字符串文字,只分配一次。

您可以通过检查地址来确认:

fprintf(stdout,"%p: %s\n", hello, hello);

示例输出:

0x563b972277c4: hello world
0x563b972277c4: hello world

if i call the print() function twice, will it allocate the memory for the hello variable twice?

是的。但是,hello 是一个 指针 并且在 64 位机器上占用 8 个字节的堆栈 space。成本实际上是无法衡量的。此外,编译器根本不需要分配变量 ,因为您永远不会尝试获取它的地址。编译器可以自由有效地将您的代码转换为:

void print()
{
    fprintf(stdout, "%s", "hello world");
}

意味着 hello 的声明不会在运行时导致任何内存分配。并非出于所有意图和目的,将 hello 作为局部变量是 cost-free.

相比之下,zero-terminated 字符串文字“hello world”仅在应用程序的数据段中分配一次。编译器可以这样做是因为它知道 C 字符串文字是只读的,因此不允许修改它。此外,在运行时不会执行 dynamic 内存分配。字符串文字的内存是静态分配的,它的生命周期就是应用程序的生命周期。

因此,您的 print 函数本质上是尽可能廉价的 — 它根本不执行任何实际分配。

您可以将代码粘贴到 compiler explorer 以查看会发生什么。从您的代码中,这是生成的 assebler:

print():                              # @print()
        mov     rcx, qword ptr [rip + stdout]
        mov     edi, offset .L.str
        mov     esi, 11
        mov     edx, 1
        jmp     fwrite                  # TAILCALL
main:                                   # @main
        push    rax
        mov     rcx, qword ptr [rip + stdout]
        mov     edi, offset .L.str
        mov     esi, 11
        mov     edx, 1
        call    fwrite
        mov     rcx, qword ptr [rip + stdout]
        mov     edi, offset .L.str
        mov     esi, 11
        mov     edx, 1
        call    fwrite
        xor     eax, eax
        pop     rcx
        ret
.L.str:
        .asciz  "hello world"

重要的部分在最后:

.L.str:
        .asciz  "hello world"

"hello world" 仅在此处声明为全局变量,每次调用函数时都会使用 print

就像这样声明:

#include<stdio.h>

const char* hello = "hello world";

void print()
{
    fprintf(stdout,"%s",hello);
}

void main()
{
    print();
    print();
}

在这种情况下,编译器看到了优化并进行了优化。但是,我不能肯定地说情况总是如此,因为它不能依赖于编译器。

  1. hello变量

自动变量在函数退出时结束它们的生命。因此,当您在函数内部时,内存只会在堆栈上分配。当函数存在时它将被释放。

  1. 字符串文字具有程序生命周期并且是存储文字的地方(通常是 .rodata 段)。这个区域在程序构建期间被填充,它在内存中的实际表示方式取决于实现