C语言多文件编译链接为1个可执行文件的简单原理

参考1:C header files and compilation/linking
参考2:计算机系统基础(一)袁春风 (符号链接部分)

我们现在有一个简单的工程,有这么几个文件

1.t1.h

extern int x;void tt();
  1. t1.c
#include "t1.h"int x;void tt(){x = 100;
}
  1. main.c
#include <stdio.h>
#include "t1.h"int main(){tt();printf("x = %d\n",x);return 0;
}

现在,我们依次来看一下

  • 头文件 和 C源文件 如何联系在一起
  • 多文件如何变成1个可执行目标文件

预处理,组合头文件与包含它的C源文件

在32位Linux系统中。

我们执行gcc -E t1.c -o t1.i,对源程序进行预处理,将头文件包含进来。

所谓预处理,就是将预编译命令处理掉,比如,把头文件的内容拷贝到C源文件中,因此,我们就能够理解.h和.c文件中全部变量的关系了,我们看一下生成的文件内容

# 1 "t1.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "t1.c"
# 1 "t1.h" 1
extern int x;void tt();
# 2 "t1.c" 2int x;void tt(){x = 100;
}

可以看到,开始有一些记录的信息,看起来应该是注释,是一些说明性信息。

然后我们看到,之前头文件的内容在这个文件里面了。如果你把它近似地看为一个源文件,你就能大概理解全局变量声明和定义,以及不要重复定义等。

对于main.c文件的预处理,道理是一样的

然后,我们将t1.imain.i分别生成其可重定位目标文件,最后链接起来,就生成了可执行目标文件。

gcc -c t1.i -o t1.o				# 生成可重定位目标文件
gcc -c main.i -o main.o			# 生成可重定位目标文件
gcc -o main main.o t1.o			# 链接,生成可执行目标文件
./main								# 运行可执行目标文件

最后,需要说明

对于函数的声明(头文件声明、外部声明、内部声明)与定义(内部定义、外部定义)

在c源文件中进行函数声明,然后使用它,在链接的时候,这个声明的函数(但未在该文件中定义)链接器会自动在其他的可重定位目标文件中寻找其定义,如果找到了就联系起来,否则就报错。这就意味着,所有的C源文件中的函数默认都是全局的,你可以在模块1声明函数a,然后调用它,在模块2中定义函数a,之后将两个模块链接起来生成可执行文件。

好吧,每次都声明很麻烦,所以,我们在模块2对应的头文件声明,然后模块1引入头文件,这样,我们就能够进行和之前一样的操作了,你知道的,头文件在预处理的时候会被包含进入源文件,这样,其实达到了和我们手动声明一样的效果。

如果你不想让函数是全局的,那就加上static,这样,只有该源文件可以使用它了!

对于变量的定义与初始化
  • 局部变量:只能在其{ }内使用
  • static变量:只能在本模块内使用
  • 全局变量:全部都可以用
    • 本模块定义
    • 外部定义extern

对于本模块使用的,没得说,就是只有这个源文件能够使用这个变量!

只讨论全局变量

  1. extern变量,不要初始化,只声明即可!
  2. 任何初始化的全局变量,全局范围内只能初始化1次,可以定义n次,但是最终内存空间中的,就是初始化的内个变量的位置,只有一个!
  3. 头文件一般包含函数声明,全局变量的定义似乎没必要,或者说,不要在头文件定义全局变量
  4. 全局变量最好少出现,出现一次也就够了,在不同编译器下,要求不一样。少用。 比如在Linux的gcc下,不同C源文件,可以出现多个int x弱符号,但是Windows的VS下就不允许。

因此说,使用全局变量,最好

  • 初始化全局变量
  • 只定义一次
  • 其他的模块如果要用,就声明extern表示引用外部定义,而不是再声明一个弱符号(不是不行,但是这样的话,gcc能用,VS就不能用)
  • 不要在头文件定义全局变量(头文件被多次包含的话…得定义多少次,恐怖)

注意

函数有

  1. 是否声明
  2. 是否定义
  3. 是否使用

对于任何模块,必须先声明,再使用,声明之后可以不定义,因为可以外部定义,但是被声明被使用的函数,必须有其定义。 要是声明之后没有使用也没有定义,其实也行,但是最好不要这样干,太奇怪了!

对于同一模块,先定义再使用也可以,但是不能先使用再定义,顺序很重要。


变量有

  • 是否定义
  • 是否初始化
  • 是否使用

然后,还涉及到不同模块之间…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/384620.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java读写二维数组到文件

1. 创建文件 使用了File类中的createrNewFile方法。 public static boolean createFile(String filename){try{File file new File(filename);file.createNewFile();return true;} catch (IOException e) {e.printStackTrace();return false;}}查阅文档可知&#xff0c;若文件…

如何掌握java API的方法

如何掌握方法的使用&#xff1f; 查文档文档不一定看得懂&#xff0c;亲自试一试就知道了&#xff01; 这俩方法没啥好说的&#xff0c;自己体会即可&#xff01; 注意&#xff01;先看原版英文文档&#xff0c;然后自己试一试&#xff0c;就能更加理解了&#xff0c;然后&a…

Leetcode1512. 好数对的数目 抽出本质原型 利用范围条件

解法1&#xff1a;暴力枚举 class Solution {public int numIdenticalPairs(int[] nums) {int count 0;for(int i 0;i < nums.length; i){for(int j i 1; j < nums.length; j){if(nums[i] nums[j])count;}}return count;} }没啥可说的&#xff0c;就是小学数学问题…

leetcode面试题 10.01. 合并排序的数组

直接排序 直接使用Java已有的方法进行排序&#xff0c;这一招…大意了&#xff01; 这题简单&#xff0c;就是个基本的排序&#xff0c;后面难题&#xff0c;可能这只是一小步&#xff0c;内个时候直接用排序算法比较合适&#xff0c;这个不合适。。 class Solution {public…

IA-32 Architecture: the function of segment regitster(CS DS SS ES)

对于IA-32架构&#xff0c;与8086不同&#xff0c;段寄存器不再是像以前一样&#xff0c;直接作为段基址&#xff0c;因为32位的寄存器直接就可以表示4GB大小&#xff0c;不需要再偏移&#xff0c;因此段寄存器的含义也发生了相应的变化。 在IA-32架构里&#xff0c;段寄存器是…

x86异常处理与中断机制(1)概述中断的来源和处理方式

参考《计算机组成》&#xff08;北京大学 MOOC&#xff09; 1 异常与中断的来源&#xff08;为什么需要中断&#xff09; 首先&#xff0c;说明一下异常和中断这两个概念。 它们两个唯一的区别&#xff0c;就是&#xff0c;没有什么区别。只是不同的地方不同的时间不同的人的…

【C language】typedef的使用:结构体、基本数据类型、数组

typedef基本数据类型 typedef int a; a abc;后面的a abc就等价于int abc typedef结构体 typedef struct a {int a;int b; } abc;abc aaa;对于上述&#xff0c;abc aaa;就等价于struct a aaa; 简而言之&#xff0c;typedef的本质&#xff0c;就是构建等价关系。 第一个例…

【C language】动态数组的创建和使用

在C语言中&#xff0c;使用malloc函数创建动态数组&#xff0c;使用一个指针指向它&#xff0c;使用下标进行访问。 unsigned long *a (unsigned long *)malloc(2 * sizeof(int)); a[0] 1000; a[1] 2000; printf("%d %d\n", a[0], a[1]); free(a);上述例子&…

x86异常处理与中断机制(2)中断向量表

补充&#xff1a;事件不仅包含中断和异常&#xff0c;还包含系统调用&#xff0c;这个属于用户主动请求的事件。 上一节&#xff0c;只有一个溢出异常&#xff0c;那么&#xff0c;如果很多异常、中断呢&#xff1f;&#xff08;中断向量表&#xff09; 另外&#xff0c;之前0…

x86异常处理与中断机制(3)中断处理过程

上一节讲完了根据中断类型号找中断服务程序的过程&#xff0c;现在着重说明一下更加完整的中断处理过程吧。 本节以8086时代的中断处理过程为例进行说明&#xff0c;主要分两大部分 硬件处理软件处理 需要注意&#xff0c;这不是绝对的&#xff0c;得看实际情况&#xff0c;…

Linux 0.11 内核解析:中断相关(1)asm.s文件中断处理分析

0 源代码 有两个版本的&#xff0c;一个是带中文注释&#xff0c;Intel格式的&#xff1b;一个是不带注释是AT&T格式的。 Linux 0.11 中文注释版 Linux 0.11 源码&#xff0c;基于《Linux内核完全注释》赵炯 1 asm.s 文件 我们先假设该文件处理的中断是无特权过渡的情况…

【精华文】C语言结构体特殊情况分析:结构体指针 / 基本数据类型指针,指向其他结构体

参考链接&#xff1a;Structure pointer pointing to different structure instance 注&#xff1a;可以查看此篇的问题和唯一的回复&#xff0c;那是相对正确的&#xff0c;不要看comment&#xff0c;有很多错误。 我是拒绝分析这种问题的&#xff0c;因为似乎没有人会这么乱用…

enum in c language

今天说说C语言中的枚举。 参考&#xff1a;Enumeration (or enum) in C 1 定义 定义一个枚举类型很容易&#xff1a; enum aa { a1, a2, a3 };这里 enum是关键字aa是枚举变量&#xff0c;也就是我们自定义类型a1,a2,a3是枚举成员 然后怎么使用呢&#xff1f; 首先&#…

信号量SIGCHLD的使用,如何让父进程得知子进程执行结束,如何让父进程区分多个子进程的结束

本教程基于 Ubuntu 20.10 gcc 10.2.0. 示例程序如果不能正常编译和执行&#xff0c;说明您系统和工具版本与我的不匹配&#xff0c;请自行查阅资料。 0 概述 先给出该信号的描述&#xff1a; SignalValueDescriptionSIGCHLD17Child status has changed (POSIX). Signal sent …

使用gdb调试多进程程序、同时调试父进程和子进程

参考: [1] GDB debugging multi-process programs [2] Debugging programs with multiple processes 根据这两篇参考链接&#xff0c;完全可以实现使用gdb同时调试父进程和子进程。 接下来说明一下可能遇到的坑 gdb8.1版本有bug&#xff0c;设置完set detach-fork-on off&…

Linux安装Ncurses库

参考&#xff1a;How To Install Ncurses Library In Linux 针对Ubuntu说明一下&#xff1a; wget https://ftp.gnu.org/pub/gnu/ncurses/ncurses-6.2.tar.gz&#xff0c;至于最新版本&#xff0c;自己看官网&#xff0c;修改一下版本号即可。tar xzf ncurses-6.2.tar.gzcd nc…

gdb 10.2的安装

参考 [1] GDB-10.2 [2] README for GDB release 个人系统 Ubuntu20.10。 注意gdb10.2需要c11语法&#xff0c;需要安装g 下载安装包wget https://ftp.gnu.org/gnu/gdb/gdb-10.2.tar.xz解压缩tar -xvzf gdb-10.2.tar.xz进入解压之后的目录mkdir buildcd build配置&#xff0c;…

gdb tui的使用

[1] GDB Text User Interface [2] GDB Text User Interface 简单来说&#xff0c;进入gdb之后&#xff0c;使用ctrl x 2就足够了。其他细节请参考上述链接&#xff0c;选一个就可以。

C语言中信号函数(signal)的使用

先来简单谈谈C语言中的信号&#xff08;signal&#xff09; 首先&#xff0c;signal是C语言库中的函数&#xff0c;它实际上是软中断&#xff0c;也就是软件发出的终端&#xff0c;本质来说&#xff0c;类似于int n。 对于接收到该软中断信号的进程&#xff0c;就会停下手头的…

UNIX哲学

参考&#xff1a; 对比Linux与Windows 使用Linux想要做某些事情的时候&#xff0c;就拆开想&#xff0c;想想我需要哪些功能&#xff0c;需要哪些工具&#xff0c;依次怎么执行&#xff0c;然后用管道建立连接&#xff0c;让数据依次流过不同的工具&#xff0c;从而得到最终结果…