step1:下载安装 Dev-C++

已经安装了 Dev-C++ 或系统中的可以跳过这步。去官网下载 Dev-C++。我昨天下载,发现有点慢,所以我把安装文件放到百度网盘了,供大家下载,下载链接为:http://pan.baidu.com/s/1pLPenDx

开始安装,记住安装位置。在安装时只能选择英文,安装完成后,第一次启动时可以选择中文。启动后,关掉。本文中我们不会用到 Dev-C++ 提供的 IDE,我们只用它目录下的 gcc 编译器。

step2:将 Dev-C++ 目录下的 gcc 编译器工具目录添加到系统环境变量

step 2.1:设置系统环境变量

找到 Dev-C++ 的安装目录下的 bin 文件目录。比如我是C:\Program Files (x86)\Dev-Cpp\MinGW64\bin。该目录下是编译程序用到的一些命令行工具,如下图:

Dev-C++ 正是调用这些工具来编译程序的。

复制该目录。在系统的文件管理器地址栏输入控制面板\系统和安全\系统,回车,打开系统设置,如下图:

点击高级系统设置,在弹出的对话框中点击环境变量

在弹出的对话框中,如下图,在系统变量的变量栏下找到Path变量,点击编辑按钮。

会再弹出一个对话框,可以看到变量值输入栏中有很多内容,鼠标选中该输入框,将光标移动到输入内容的最后,添加一个英文分号;,然后在后面粘贴之前找到的 gcc 编译器命令行工具目录,我的是C:\Program Files (x86)\Dev-Cpp\MinGW64\bin,然后点确定,依次关闭所有的弹窗。

step2.2:验证

打开开始,输入cmd,回车。打开了控制台终端,输入gcc --version,如果输入如下图所示,则说明设置成功。

如果显示错误信息,可能是你前面哪部走错了。或者你需要重启系统。

step3:编辑程序

和前一篇文章一样,我们要编辑三个程序源文件。先创建一个目录,再使用你最喜欢的编辑器创建下面三个文件:

myfile.h

//myfile.h
// 这里只有三个函数声明
void func1();
void func2();
void func3();
  • 1
  • 2
  • 3
  • 4
  • 5

myfile.c

// myfile.c
// 这里是3个函数实现
#include <stdio.h>
#include "myfile.h" void func1()
{
printf("func1\n");
} void func2()
{
printf("func2\n");
} void func3()
{
printf("func3\n");
}

main.c

# include <stdio.h>
#include "myfile.h" int main()
{
func1();
func2();
func3(); return 0;
}

step4:编译程序

打开一 cmd 窗口,输入上面三个程序所在的盘符,然后用cd命令跳转到程序所在目录下。

编译myfile.c生成中间文件

在 cmd 中输入:

gcc -c myfile.c
  • 1

-c表示只编译成二进制的中间文件,但不链接。你会看到程序所在目录下多了一个myfile.o文件

编译main.c生成中间文件

gcc -c main.c
  • 1

同样会在当前目录下生成一个 main.o 文件。

链接main.omyfile.o,生成最终的可执行文件:

gcc main.o myfile.o
  • 1

同样会在目录下生成一个a.exe,即最终的可执行文件。

检测一下a.exe是否能执行:

a.exe
  • 1

输出如下图所示:

说明我们的编译成功了。

你也可以直接使用gcc main.c myfile.c来完成整个过程,这种情况下,编译器还是会在背后走这些步骤,只不过只把最后结果给你看。

在上面的每一步编译过程中,我们都可以用-o参数来指定生成文件的文件名。比如gcc main.o myfile.o -o main.exe生成的可执行文件名为main.exe

C 程序的模块化

C 程序的编译过程

C 程序的编译单位为每个 .c 源文件,整个编译过程大致可以分为四个阶段:预处理、编译、汇编、链接。每个编译单元都会经过预处理、编译,最后将各个单元生成的中间文件链接到一起形成可执行文件。

预处理阶段的工作主要包括:宏替换、头文件包含内容替换等。

编译阶段的主要工作是:将预处理后的源文件转换成汇编代码。

汇编阶段的主要工作是:将上一阶段生成的汇编代码编译成二进制文件,即中间文件。

链接阶段的主要工作是:将各中间文件链接到一起,生成可执行文件。(如果程序使用了静态链接库,链接阶段还会将静态库导入到可执行文件中,目前我们不需要了解。)

上面提到的编译过程不一定完整和准确,但对于我们理解如何编译多个源文件的程序已经够用了。

以前面我们编译的程序为例,我们的整个编译过程如下图所示。

特别提一下,在预处理阶段会进行头文件包含的替换工作。比如将#include "myfile.h"替换为myfile.h文件中的内容。myfile.c替换后的结果大概如下:

/*
* stdio.h 的替换内容
*/
void func1();
void func2();
void func3();
void func1()
{
printf("func1\n");
} void func2()
{
printf("func2\n");
} void func3()
{
printf("func3\n");
}

main.c替换后的结果也可以这样脑补。

想要前进,我们还得补充一下编译器在编译和链接阶段时所作的工作。我们知道main.o是从main.c生成,main.c中调用了三个函数,而这三个函数在main.c中并没有实现。那编译器是怎么处理的呢?是这样的:编译器在编译main.c时看到三个未实现的函数声明,就根据它们的函数声明给它们生成了各自的“身份ID”,不同的函数声明会生成不同的“身份ID”,“身份ID ”是唯一的。编译器暂且将这些“身份ID”记录在中间文件中。在编译myfile.o时同样会对三个函数生成三个“身份ID”,由于myfile.c中的函数声明和main.c中的函数声明一样,所以生成的三个“身份ID”也一样。最后在链接main.omyfile.o时,“身份ID”就对上了,前者有调用,后者有实现,也就能正确的生成可执行文件了。

C 程序的模块化

其实从前面的编译过程我们就可以直观的知道,不止程序的编写是分模块的,程序的编译过程也是分模块的,各个源文件分开编译后组装。C 程序的编译单元是 .c 文件,每个 .c 源文件都会生成一个 .o 中间文件,最后所有的.o 文件链接成一个可执行文件。只有在最后的链接阶段,.o 文件才会联系到一起。

所以我们修改了某个源文件,只需要重新编译这个源文件即可,没修改的文件不需要重新编译,当然,最后得重新链接一次。假如我们现在修改了myfile.c,我们只想重新生成myfile.o,然后链接myfile.omain.o即可。

所以 C 程序的模块化,即方便了程序员按逻辑组织程序,也减轻了编译器的工作,将每次修改代码后的重编译工作量减到最小。

模块之间的依赖

通过前面对编译过程的分析,我们可以得出这样的结论,main.o依赖于main.cmyfile.hmyfile.o依赖于myfile.cmyfile.h。而a.exe依赖于main.omyfile.o。整个依赖树如下:

          a.exe
|
-------------
| |
main.o myfile.o
| |
--------- -------
| | | |
main.c myfile.h myfile.c

如果某个文件的依赖项改变了,这个文件就得重新生成。myfile.h改变了,main.omyfile.o都得重新生成,进一步a.exe也得重新生成。如果只是myfile.c改变了,myfile.o要重新生成,a.exe也要重新生成。

这个程序十分简单,依赖关系也比较简单,所以我们可以在命令行里手动编译它们,实际上我们是在靠大脑在维护它们的依赖关系。如果程序规模变大,依赖关系将复杂到我们的大脑没办法维护。如果记不住依赖关系,我们一股脑儿的全部重新编译又太耗费时间(大的程序从头编译一次可能会好几个小时,十几个小时,你怕不怕)。这时候我们就得依赖于工具了,工具有半自动和全自动工具。半自动工具,比如 makefile 需要我们手动写一次依赖关系,全自动工具,比如像 VS 和 Dev-C++会全自动维护依赖关系,不需要我们操任何心。我们用 IDE 创建工程时,IDE 在工程目录下创建的那些文件,有一些是中间文件,有一些是用来记录依赖关系的。

结束

恭喜你看到了这里!我们学会了手动编译程序,大致知道了编译器编译程序时做了哪些工作。明白了这些就好,在实际编程时还是使用 IDE 比较方便。我们和其他选手一样用 IDE 编程,但和他们不一样,我们知道 IDE 帮我们做了哪些事,我们简直是看透一切的(男/女)人,哈哈哈~~~

最新文章

  1. oracle 常用视图和表
  2. OAF_OAF Debug And Log调试和记录工具的详解(案例)
  3. svn:revert to this version 和 revert changes from this version的区别
  4. Day3 set集合、函数和装饰器
  5. 合并 CentOS 6.8 的两个ISO镜像
  6. 100个Myeclipse6.5免费注册码
  7. 大数相加 Big Num
  8. Job for docker.service failed because the control process exited with error
  9. 【Teradata】四舍五入函数
  10. 常用oracle hints
  11. unity5.x中的关节和布料
  12. jupyter notebook 在mac OS上的安装
  13. centos7 yum install timeout
  14. Redis Commands(1)
  15. bzoj1082
  16. Unity5.X 新版AssetBundle使用方案及策略
  17. PHP安装posix、pctl扩展
  18. LinkedList剖析
  19. Python+Django+js+echarts引入本地js文件的操作方法
  20. 长尾分布,重尾分布(Heavy-tailed Distribution)

热门文章

  1. C语言:赋值
  2. 一文读懂k8s rbac 权限验证
  3. PO封装设计模式 -- App移动端测试
  4. c# checkedListBox设置多列横向显示 经验总结
  5. java跨平台性说明
  6. js 正序、倒序、按字段排序方法
  7. python里面的MD5加密 ---# hashlib包的使用
  8. 第四篇 -- CSS基础
  9. Leetcode_丑数问题
  10. 记一次 .NET 某WMS仓储打单系统 内存暴涨分析