gcc是否将原型视为函数,其参数是否分配了内存?

gcc是否将原型视为函数,其参数是否分配了内存?,c,gcc,compiler-construction,language-design,C,Gcc,Compiler Construction,Language Design,我有一个奇怪的问题,关于C语言的设计,实际上是关于编程和语言设计 这就是它的基础:如果我调用了一个只有原型而没有赋值的函数,我会调用实际的C函数数据结构吗?换句话说,这在任何意义上都是一个真正的功能,还是原型由gcc以某种有代表性的方式处理,可能使用不同的数据结构?这个问题中的具体问题是,是否为原型声明的参数分配了内存,以及是否创建了空范围 Gcc当然不允许您这样做,但如果它编写的机器代码与正常情况下相同,并且我尝试调用一个仅为原型的函数,那么失败的原因是: 原型并不是所有意义上的功能 这些参数

我有一个奇怪的问题,关于C语言的设计,实际上是关于编程和语言设计

这就是它的基础:如果我调用了一个只有原型而没有赋值的函数,我会调用实际的C函数数据结构吗?换句话说,这在任何意义上都是一个真正的功能,还是原型由gcc以某种有代表性的方式处理,可能使用不同的数据结构?这个问题中的具体问题是,是否为原型声明的参数分配了内存,以及是否创建了空范围

Gcc当然不允许您这样做,但如果它编写的机器代码与正常情况下相同,并且我尝试调用一个仅为原型的函数,那么失败的原因是:

  • 原型并不是所有意义上的功能

  • 这些参数实际上不是声明,所以它们不是 用正确的地址和正常值表示分配的内存 行为

  • 因为没有花括号,gcc没有,也不能, 生成要添加到堆栈中的此“函数”的作用域, 由于没有作用域,所以使参数声明变得荒谬 在中声明它们(因此它们不是-因此没有地址)

  • 创建了一个作用域,否则其内容可能会出现在 堆栈,但由于中没有指令,执行将终止 在存储器中推进程序的功能块

  • 从技术上讲,您可以像 函数,问题是它们什么都不做

  • 还有一件事我完全错过了

  • 我不知道为什么这个问题对我来说很重要,但我想如果有什么比什么都重要的话,这有点让我发疯


    谢谢大家

    当您在不使用
    -c
    标志的情况下使用gcc时,它会做两件事:将源文件编译为object和files,然后将object文件链接到最终的可执行文件(或库文件)。因此,从这个意义上讲,您可以将其视为两个工具,事实上,对于链接步骤,gcc确实调用了单独的工具ld

    现在,当gcc看到一个函数原型时会发生什么?它将有关函数签名的信息存储在其内部数据结构中,因此它知道如何对函数调用进行类型检查,以及如何为函数调用生成代码(根据类型,它可能必须插入隐式转换的代码,并且生成的代码在调用变量函数时看起来不同)。原型不会导致生成任何实际代码

    当gcc看到一个实际的函数定义时,它也会在其内部数据结构中存储相同的信息,但它也会为函数体生成代码,并将函数名和生成代码的地址存储在对象的符号表中

    现在,对于函数调用,编译器对一个仅仅是原型化的函数执行的操作与对实际实现的函数执行的操作相同。事实上,它甚至不知道函数的定义是否存在,因为编译器一次只能看到一个c文件(或者更确切地说是一个编译单元),并且定义可能存在于另一个文件中。那么编译器做什么呢?它将参数推送到系统堆栈上和/或存储在寄存器中,具体取决于参数的数量和类型以及调用约定。然后使用函数名作为符号添加对函数的调用

    无论是否定义了所有函数,这都将起作用。如果只执行
    gcc-c
    ,则未定义函数不会出现错误

    现在ld做什么?它遍历所有对象文件,并将其内容一起复制到最终的可执行文件或库文件中。在执行此操作时,它将函数和变量的符号名替换为它们在可执行文件中的实际地址。如果未定义函数,则会在这一部分出现错误

    那么,如果它允许您调用未定义的函数,会发生什么呢?不可能。当您调用未定义的函数作为一种健全性检查时,它不会拒绝创建可执行文件,它拒绝创建可执行文件是因为它不能。当没有函数定义时,就没有用来替换符号的地址。所以无法链接这些文件


    所以我猜答案是“a”:原型不是函数,因为它们根本不存在于生成的对象文件中。函数将只存在于包含其实际定义的对象文件中,如果这样一个文件不存在(或有多个),则这是一个错误。

    当您在没有
    -c
    标志的情况下使用gcc时,它会做两件事:将源文件编译为对象和文件,然后将对象文件链接到最终的可执行文件中(或库)文件。因此,从这个意义上讲,您可以将其视为两个工具,事实上,对于链接步骤,gcc确实调用了单独的工具ld

    现在,当gcc看到一个函数原型时会发生什么呢?它在其内部数据结构中存储有关函数签名的信息,因此它知道如何对函数调用进行类型检查,以及如何为函数调用生成代码(取决于类型,它可能必须插入隐式转换的代码,并且生成的代码在调用变量函数时看起来不同。例如,原型不会导致生成任何实际代码

    当gcc看到一个实际的函数定义时,它也会在其内部数据结构中存储相同的信息,但它也会为函数体生成代码,并将函数名和生成代码的地址存储在对象的符号表中

    现在,对于函数调用,编译器对一个proto做同样的事情
    unsigned int fun1 ( unsigned int x );
    unsigned int fun0 ( unsigned int x )
    {
        return(fun1(x)+1);
    }
    
    arm-none-eabi-gcc -c -O2 -save-temps fun0.c
    
        .cpu arm7tdmi
        .eabi_attribute 20, 1
        .eabi_attribute 21, 1
        .eabi_attribute 23, 3
        .eabi_attribute 24, 1
        .eabi_attribute 25, 1
        .eabi_attribute 26, 1
        .eabi_attribute 30, 2
        .eabi_attribute 34, 0
        .eabi_attribute 18, 4
        .file   "fun0.c"
        .text
        .align  2
        .global fun0
        .syntax unified
        .arm
        .fpu softvfp
        .type   fun0, %function
    fun0:
        @ Function supports interworking.
        @ args = 0, pretend = 0, frame = 0
        @ frame_needed = 0, uses_anonymous_args = 0
        push    {r4, lr}
        bl  fun1
        pop {r4, lr}
        add r0, r0, #1
        bx  lr
        .size   fun0, .-fun0
        .ident  "GCC: (GNU) 6.2.0"
    
    00000000 <fun0>:
       0:   e92d4010    push    {r4, lr}
       4:   ebfffffe    bl  0 <fun1>
       8:   e8bd4010    pop {r4, lr}
       c:   e2800001    add r0, r0, #1
      10:   e12fff1e    bx  lr
    
    extern unsigned int fun2 ( unsigned int );
    unsigned int fun1 ( unsigned int x )
    {
        return(fun2(x)+2);
    }
    
    00000000 <fun1>:
       0:   e92d4010    push    {r4, lr}
       4:   ebfffffe    bl  0 <fun2>
       8:   e8bd4010    pop {r4, lr}
       c:   e2800002    add r0, r0, #2
      10:   e12fff1e    bx  lr
    
    unsigned int fun2 ( unsigned int  x)
    {
        return(x+3);
    }
    
    00000000 <fun2>:
       0:   e2800003    add r0, r0, #3
       4:   e12fff1e    bx  lr
    
    .globl _start
    _start:
        mov sp,#0x8000
        mov r0,#0
        bl fun0
        b .
    
    00008000 <_start>:
        8000:   e3a0d902    mov sp, #32768  ; 0x8000
        8004:   e3a00000    mov r0, #0
        8008:   eb000000    bl  8010 <fun0>
        800c:   eafffffe    b   800c <_start+0xc>
    
    00008010 <fun0>:
        8010:   e92d4010    push    {r4, lr}
        8014:   eb000002    bl  8024 <fun1>
        8018:   e8bd4010    pop {r4, lr}
        801c:   e2800001    add r0, r0, #1
        8020:   e12fff1e    bx  lr
    
    00008024 <fun1>:
        8024:   e92d4010    push    {r4, lr}
        8028:   eb000002    bl  8038 <fun2>
        802c:   e8bd4010    pop {r4, lr}
        8030:   e2800002    add r0, r0, #2
        8034:   e12fff1e    bx  lr
    
    00008038 <fun2>:
        8038:   e2800003    add r0, r0, #3
        803c:   e12fff1e    bx  lr
    
    fun1.c: In function ‘fun1’:
    fun1.c:5:12: warning: implicit declaration of function ‘fun2’ [-Wimplicit-function-declaration]
         return(fun2(x)+2);
                ^~~~
    
    00000000 <fun1>:
       0:   e92d4010    push    {r4, lr}
       4:   ebfffffe    bl  0 <fun2>
       8:   e8bd4010    pop {r4, lr}
       c:   e2800002    add r0, r0, #2
      10:   e12fff1e    bx  lr
    
    extern float fun2 ( unsigned int );
    unsigned int fun1 ( unsigned int x )
    {
        return(fun2(x)+2);
    }
    
    00000000 <fun1>:
       0:   e92d4010    push    {r4, lr}
       4:   ebfffffe    bl  0 <fun2>
       8:   e3a01101    mov r1, #1073741824 ; 0x40000000
       c:   ebfffffe    bl  0 <__aeabi_fadd>
      10:   ebfffffe    bl  0 <__aeabi_f2uiz>
      14:   e8bd4010    pop {r4, lr}
      18:   e12fff1e    bx  lr
    
    void more_fun ( void );
    
    unsigned int more_fun ( unsigned int );