标准C不完全声明

标准C不完全声明,及头文件

本文主要介绍标准C的不完全声明及头文件。

1. One-pass complier

Question 1.0. 什么是 ‘one-pass complier’ ?

Answers 1.0. ‘One-pass complier’ [4] 是一种和 ‘multi-pass complier’ [3] 相对的编译方式。顾名思义,基于 ‘one-pass complier’ 的编译器在编译的时候,通常仅仅顺序地遍历一次源代码;相反地,基于 ‘multi-pass complier’ 的编译器在编译的时候,通常需要遍历多次源代码。

显然,’one-pass compliers’ 比 ‘multi-pass compliers’ 需要更少的代码链接时间 [6],因为’one-pass compliers’ 默认用户已经根据 ‘先声明后使用’ 的原则 [7] 顺序地组织好源代码。一旦用户没有按照该原则 [7] 进行代码的组织,编译器立即返回相关错误,编译失败;而在相同情况下,基于 ‘multi-pass compliers’ 的编译器并不会因为首次找不到声明代码段而抛出编译错误,而是顺序地遍历多次源代码,每一次遍历的结果作为下一次遍历的输入,不断优化迭代指令,直至最终编译完成,避免声明代码后置导致单次顺序编译失败的情况的发生

举个例子,标准C及其衍生语言,Python 等是典型的 ‘one-pass compliers’ 类型的编译器,Java,Go 是典型的 ‘multi-pass compliers’ 类型的编译器,下面我们以C,Go 代码例子来看看两者的区别,

  • 标准C 实现的 add.c 的源代码如下,

    
    #include <stdio.h>
    
    
    void main()
    {
    int a = 1;
    int b = 2;
    
    printf("%d", add(a, b));
    }
    
    int add(int a, int b)
    {
    return a + b;
    }

    编译失败:”add.c:8:15: warning: implicit declaration of function ‘add’ [-Wimplicit-function-declaration]” – gcc version 5.4.0 20160609

  • Go 实现的 add.go 的源代码如下,

    package main
    
    import "fmt"
    
    func main() {
    fmt.Println(add(42, 13))
    }
    
    func add(x int, y int) int {
    return x + y
    }

    编译成功:add.go 在 go version go1.10.1 linux/amd64 下成功编译

    ~$go build add.go

    ~$./add

    55

Question 1.1. 什么是不完全声明? 为什么不完全声明能解决 声明代码后置导致单次顺序编译失败情况

Answer 1.1. 不完全声明又常被称为前置声明 [1, 2],通常是 one-pass compliers 应对 “声明代码后置导致单次顺序编译失败情况” 的一种解决方案。本质上,不完全声明是一种不完整的 class 或者说是 struct,因为它只告诉编译器某个 class / struct 存在的事实,并没告诉编译器该 class / struct 完整的内部信息,比如函数内部运算等——所以它叫不完全声明。另一方面,不完全声明的代码段通常在预编译的时候被编译器处理——所以不完全声明也常被叫作前置声明。

不完全声明是如何解决 声明代码后置导致单次顺序编译失败情况 的呢?以标准C来举个例子,

  • 方法一: 在.c 文件中进行不完全声明,add.c的代码如下,

    
    #include <stdio.h>
    
    
    int add(int a, int b); // forward declaration
    
    void main()
    {
    int a = 1;
    int b = 2;
    
    printf("%d", add(a, b));
    }
    
    int add(int a, int b)
    {
    return a + b;
    }

    编译成功,gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu~16.04.10)

    ~$ gcc add.c -o add

    ~$ ls

    add.c add

    ~$ ./add

    3

  • 方法二:在.h 文件中进行不完全声明,add.h 的代码如下,

    
    #ifndef add_h
    
    
    #define add_h
    
    
    
    #include <stdio.h>
    
    
    int add(int a, int b); // forward declaration
    
    
    #endif
    

    add.c 的代码如下,

    
    #include "add.h"
    
    
    void main()
    {
    int a = 1;
    int b = 2;
    
    printf("%d", add(a, b));
    }
    
    int add(int a, int b)
    {
    return a + b;
    }

    编译成功,gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu~16.04.10)

    ~$ gcc add.c -o add

    ~$ ls

    add.c add

    ~$ ./add

    3

    方法一方法二 哪个好?为什么好?好在哪里?

    通常来讲,我们推荐将不完全声明写在 .h 文件而非 .c 文件 [2]。这样做的好处有以下几点:其一,降低程序的耦合度,特别是在项目规模较大的时候。换句话说,将成千上万行代码全部放到一个 .c 文件,显然不能说是一个好的做法,这样不便于程序的阅读和修改。其二,方便模块化编程。换句话说,将不同代码段 “封装” 成不同的模块,需要即调用,增加编程效率。因此,我们推荐将不完全声明写在.h 头文件中。

Question 1.2. 为什么不完全声明提供的是不完整的信息 (比如函数名,函数返回类型), 而不是完整的信息?

Answer 1.2. 这个问题存在一个逻辑矛盾:不完全声明的定义是:在classstruct 的声明代码 (:声明代码是名词,表示正式实现的class或者struct 代码;前置声明是动词,表示在声明代码之前提前告知编译器声明代码的存在的事实) 之前提前告知编译器声明代码的存在,避免编译器顺序编译的时候找不到后置的声明代码

因此,如果不完全声明提供的是完整的classstruct 的实现,就会出现两个问题:其一,和不完全声明的定义相矛盾,即自我矛盾。其二,编译器无法正常编译,出现classstruct重定义的编译错误。其三,C编译器设计时期内存消耗是考虑在内的,在one-pass compliers的技术框架内,满足一定编程灵活性的前提下,代码应该尽可能精简。举个例子,

add.h 的实现如下,

#ifndef add_h
#define add_h

#include <stdio.h>

// int add(int a, int b); // forward declaration

int add(int a, int b)
{
    return a + b;
}

#endif

add.c 的实现如下,

#include "add.h"

void main()
{
    int a = 1;
    int b = 2;

    printf("%d", add(a, b));
}

int add(int a, int b)
{
    return a + b;
}

编译失败,gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu~16.04.10)

~$ gcc add.c -o add

add.c:12:5: error: redefinition of ‘add’

References

[1. Include diractive] https://en.wikipedia.org/wiki/Include_directive

[2. 不完全声明] http://umich.edu/~eecs381/handouts/IncompleteDeclarations.pdf

[3. multi-time compliers] https://en.wikipedia.org/wiki/Multi-pass_compiler

[4. one-pass compliers] https://en.wikipedia.org/wiki/One-pass_compiler

[5. one-pass compliers problem] https://en.wikipedia.org/wiki/One-pass_compiler

[6. link time] https://en.wikipedia.org/wiki/Link_time

[7. forward declaration] https://en.wikipedia.org/wiki/Forward_declaration

猜你喜欢

转载自blog.csdn.net/Canhui_WANG/article/details/81477869