链接详解

我们知道,C语言源代码生成可执行文件需要两个步骤:编译和链接。C语言的很多默认行为和链接的过程相关,这篇笔记我们介绍链接过程的相关知识。

多目标文件链接

下面例子中,两个.c源代码文件编译链接在一起。

#直接编译链接
gcc main.c stack.c -o main

#分别编译连接
gcc -c main.c
gcc -c stack.c
gcc main.o stack.o -o main

链接的过程是由一个链接脚本(Linker Script)控制的,链接脚本决定了给每个段(Segment)分配什么地址,如何对齐,哪个段在前,哪个段在后,哪些段合并到同一个段,另外链接脚本还要插入一些符号到最终生成的文件中,例如__bss_start_edata_end等。

如果用ld做链接时没有用-T选项指定链接脚本,则使用ld的默认链接脚本,默认链接脚本可以用ld --verbose命令查看。

extern和static的简单理解

我们看下面例子代码。

stack.c

int container[100];
int *top_ptr = container;
int is_empty = 1;

extern void push(int i);
extern void pop(void);
extern int top(void);

void push(int i)
{
    if(!is_empty)
    {
        top_ptr++;
    }
    else
    {
        is_empty = 0;
    }
    *top_ptr = i;
}

void pop(void)
{
    if(top_ptr != container)
    {
        top_ptr--;
        if(top_ptr == container)
        {
            is_empty = 1;
        }
    }
}

int top(void)
{
    return *top_ptr;
}

main.c

#include <stdio.h>

int main(void)
{
    push(1);
    push(2);
    push(3);
    printf("%d\n", top());
    pop();
    printf("%d\n", top());
    pop();
    printf("%d\n", top());
    pop();
    return 0;
}

extern关键字表示这个标识符具有External Linkage。push这个标识符具有External Linkage指的是:如果把main.cstack.c链接在一起,如果pushmain.cstack.c中都有声明(在stack.c中的声明同时也是定义),那么这些声明指的是同一个函数,链接之后是同一个GLOBAL符号,代表同一个地址。函数声明中的extern也可以省略不写,不写extern的函数声明也表示这个函数具有External Linkage。

如果用static关键字修饰一个函数或变量声明,则表示该标识符具有Internal Linkage,函数只在那个文件能多次声明;如果在另一个.c文件中声明,编译器就认为这个函数不是原来那个函数了。

注意,变量声明和函数声明有一点不同,函数声明的extern可写可不写,而变量声明如果不写extern意思就完全变了,如果变量在函数中,不写extern就表示在函数中定义一个局部变量。

另外要注意,extern的变量声明不能进行初始化,因为extern的变量声明不会开辟内存空间,自然也不能进行初始化。

实际上externstatic的规则更复杂一些,但这样简单理解不影响使用。

头文件

C语言中,除了.c源代码文件,比较常见的还有.h头文件。我们编写代码时,可以使用#include预处理指令引入头文件。

引入头文件使用<>和""的区别

  • <>:gcc先查-I指定的目录,再查系统头文件目录
  • "":gcc先查包含头文件的.c文件所在的目录,然后查找-I指定的目录,再找系统头文件目录
  • .h头文件和.c源文件不在同一目录,也可以在编译时由-I参数指定给gcc
  • #include预处理指令也可以带有路径,如#include "stack/stack.h"

防止头文件重复包含

头文件重复包含会引起函数重复声明,导致编译报错。使用#ifndef #define #endif宏可以巧妙的实现防止头文件重复包含,这是一个相当常见的技巧。

#ifndef STACK_H
#define STACK_H

extern void push(int i);
extern void pop(void);
extern int top(void);

#endif
作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap