真正的程序员都应该搞清楚编译和链接

C语言编程全过程 <br /><br /> C语言编程全过程<br />       C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作系统的启动代码和用到的库文件进行组织形成最终生成可执行代码的过程。过程图解如下:<br /><br /><br />        从图上可以看到,整个代码的编译过程分为编译和链接两个过程,编译对应图中的大括号括起的部分,其余则为链接过程。<b 阅读详情

前言

作者:刘欢(不是刘欣!),西邮Linux兴趣小组大牛。

本文原载于陈莉君老师的公众号“Linux内核之旅”,陈老师是西安邮电大学教授,十多年来一直致力于推动Linux在中国的发展,深入研究Linux内核相关理论以及技术。 陈老师也是我的新书《码农翻身》的审稿人。

从Hello World开始

有位学弟想让我说说编译和链接的简单过程,我觉得几句话简单说的话也没什么意思,索性写篇博文稍微详细的解释一下吧。

其实详细的流程在经典的《Linkers and Loaders》和《深入理解计算机系统》中均有描述,也有国产的诸如《程序员的自我修养——链接、装载与库》等大牛著作。不过,我想大家恐怕很难有足够的时间去研读这些厚如词典的书籍。正巧我大致翻阅过其中的部分章节,干脆也融入这篇文章作为补充吧。

我的环境:Fedora 16 i686 kernel-3.6.11-4 gcc 4.6.3

其实Windows下的MSVC的编译器在编译过程中的流程是差不多的,只是具体调用的程序和使用的参数不同罢了。不过为了描述的流畅性,我在行文中不会涉及MSVC的具体操作,使用Windows的同学可以自行搜索相关指令和参数。但是作为Linuxer,我还是欢迎大家使用Linux系统。

闲话不多说了,我们进入正题。在正式开始我们的描述前,我们先来引出几个问题:

C语言代码为什么要编译后才能执行?整个过程中编译器都做了什么?

C代码中经常会包含头文件,那头文件是什么?C语言库又是什么?

有人说main函数是C语言程序的入口,是这样吗?难道就不能把其它函数当入口?

不同的操作系统上编译好的程序可以直接拷贝过去运行吗?

如果上面的问题你都能回答的话,那么后文就不用再看下去了。因为本文是纯粹的面向新手,所以注定了不会写的多么详细和深刻。如果你不知道或者不是很清楚,那么我们就一起继续研究吧。

我们就以最经典的HelloWorld程序为例开始吧。我们先使用vim等文本编辑器写好代码,接着在终端执行命令 

gcc HelloWorld.c -o HelloWorld 

输出了可执行文件HelloWorld,最后我们在终端执行 ./HelloWorld,顺利地显示了输出结果。

640?wx_fmt=jpeg

可是,简单的命令背后经过了什么样的处理过程呢?gcc真的就“直接”生成了最后的可执行文件了吗?

当然不是,我们在gcc编译命令行加上参数 –verbose要求gcc输出完整的处理过程(命令行加上 -v 也行),我们看到了一段较长的过程输出。

640?wx_fmt=jpeg

输出结果我们就不完整截图了,大家有兴趣可以自己试验然后试着分析整个流程。

一图胜千言,我们先上一张图吧。这是gcc编译过程的分解图,我在网上找不到满意的,就自己画了一张简单的,大家将就着看吧。

640?wx_fmt=jpeg

从图中我们大致可以看出gcc处理HelloWorld.c的大致过程:

预处理(Prepressing)—>编译(Compilation)—>汇编(Assembly)—>链接(Linking)

括号中我注明了各个过程中实际执行任务的程序名称:预处理器cpp、编译器cc1、汇编器as以及最后的链接器ld。

预处理

我们一步一步来看,首先是预处理,我们看看预处理阶段对代码进行了哪些处理。

我们在终端输入指令 gcc -E HelloWorld.c -o HelloWorld.i,然后我们打开输出文件。

640?wx_fmt=jpeg

首先是大段大段的变量和函数的声明,汗..我们的代码哪里去了?我们在vim的普通模式中按下shift+g(大写G)来到最后,终于在几千行以后看到了我们可怜兮兮的几行代码。

640?wx_fmt=jpeg

前面几千行是什么呢?

其实它就是 /usr/include/stdio.h 文件的所有内容,预处理器把所有的#include替换为实际文件的内容了。这个过程是递归进行的,所以stdio.h里面的#include也被实际内容所替换了。

而且我在HelloWorld.c里面的所有注释被预处理器全部删除了。就连printf语句前的Tab缩进也被替换为一个空格了,显得代码都不美观了。

时间关系,我们就不一一试验处理的内容了,我直接给出预处理器处理的大致范围吧。

展开所有的宏定义并删除 #define

处理所有的条件编译指令,例如 #if #else #endif #ifndef …

把所有的 #include 替换为头文件实际内容,递归进行

把所有的注释 // 和 / / 替换为空格

添加行号和文件名标识以供编译器使用

保留所有的 #pragma 指令,因为编译器要使用

……

基本上就是这些了。在这里我顺便插播一个小技巧,在代码中有时候宏定义比较复杂的时候我们很难判断其处理后的结构是否正确。这个时候我们呢就可以使用gcc的-E参数输出处理结果来判断了。

前文中我们提到了头文件中放置的是变量定义和函数声明等等内容,这些到底是什么东西呢?其实在比较早的时候调用函数并不需要声明,后来因为“笔误”之类的错误实在太多,造成了链接期间的错误过多,所有编译器开始要求对所有使用的变量或者函数给出声明,以支持编译器进行参数检查和类型匹配。

头文件包含的基本上就是这些东西和一些预先的宏定义来方便程序员编程。

其实对于我们的HelloWorld.c程序来说不需要这个庞大的头文件,只需要在main函数前声明printf函数,不需要#include即可通过编译。

声明如下:

1int printf(const char *format, ...);

这个大家就自行测试吧。另外再补充一点,gcc其实并不要求函数一定要在被调用之前定义或者声明(MSVC不允许),因为gcc在处理到某个未知类型的函数时,会为其创建一个隐式声明,并假设该函数返回值类型为int。

但gcc此时无法检查传递给该函数的实参类型和个数是否正确,不利于编译器为我们排除错误(而且如果该函数的返回值不是int的话也会出错),所以还是建议大家在函数调用前,先对其定义或声明。

编译

预处理部分说完了,我们接着看编译。

那么什么是编译?一句话描述:编译就是把预处理之后的文件进行一系列词法分析、语法分析、语义分析以及优化后生成的相应汇编代码文件。

这一部分我们不能展开说了,一来我没有系统学习过编译原理的内容不敢信口开河,二来这部分要是展开去说需要很厚很厚的一本书了,细节大家就自己学习《编译原理》吧,相关的资料自然就是经典的龙书、虎书和鲸书了。

gcc怎么查看编译后的汇编代码呢?

命令是 gcc -S HelloWorld.c -o HelloWorld.s,这样输出了汇编代码文件HelloWorld.s,其实输出的文件名可以随意,我是习惯使然。

顺便说一句,这里生成的汇编是AT&T风格的汇编代码,如果大家更熟悉Intel风格,可以在命令行加上参数 -masm=intel ,这样gcc就会生成Intel风格的汇编代码了(如图,这个好多人不知道哦)。不过gcc的内联汇编只支持AT&T风格,大家还是找找资料学学AT&T风格吧。

640?wx_fmt=jpeg

汇编

再下来是汇编步骤,我们继续用一句话来描述:汇编就是将编译后的汇编代码翻译为机器码,几乎每一条汇编指令对应一句机器码。

这里其实也没有什么好说的了,命令行 gcc -c HelloWorld.c 可以让编译器只进行到生成目标文件这一步,这样我们就能在目录下看到HelloWorld.o文件了。

Linux下的可执行文件以及目标文件的格式叫作ELF(Executable Linkable Format)。

其实Windows下的PE(Portable Executable)也好,ELF也罢,都是COFF(Common file format)格式的一种变种,甚至Windows下的目标文件就是以COFF格式去存储的。

不同的操作系统之间的可执行文件的格式通常是不一样的,所以造成了编译好的HelloWorld没有办法直接复制执行,而需要在相关平台上重新编译。当然了,不能运行的原因自然不是这一点点,不同的操作系统接口(windows API和Linux的System Call)以及相关的类库不同也是原因之一。

由于本文的读者定位,我们不能详细展开说了,有相关需求的同学可以去看《Windows PE权威指南》和《程序员的自我修养》去详细了解。

静态链接和动态链接

链接这个话题延伸之后完全可以跑到九霄云外去,为了避免本文牵扯到过多的话题导致言之泛泛,我们先设定本文涉及的范围。

我们今天只讨论链接进行的大致步骤及其规则、静态链接库与动态链接库的创建和使用这两大块的问题。至于可执行文件的加载、可执行文件的运行时储存器映像之类的内容我们暂时不讨论。

首先,什么是链接?我们引用CSAPP的定义:链接(linking)是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或被拷贝)到存储器并执行。

需要强调的是,链接可以执行于编译时(compile time),也就是在源代码被翻译成机器代码时;也可以执行于加载时,也就是在程序被加载器(loader)加载到存储器并执行时;甚至执行于运行时(run time),由应用程序来执行。

说了这么多,了解链接有什么用呢?生命这么短暂,我们干嘛要去学习一些根本用不到的东西。当然有用了,继续引用CSAPP的说法,如下:

理解链接器将帮助你构造大型程序。

理解链接器将帮助你避免一些危险的编程错误。

理解链接将帮助你理解语言的作用域是如何实现的。

理解链接将帮助你理解其他重要的系统概念。

理解链接将使你能够利用共享库。

……

言归正传,我们开始吧。为了避免我们的描述过于枯燥,我们还是以C语言为例吧。想必大家通过我们在上篇中的描述,已经知道C代码编译后的目标文件了吧。目标文件最终要和标准库进行链接生成最后的可执行文件。那么,标准库和我们生成的目标文件是什么关系呢?

其实,任何一个程序,它的背后都有一套庞大的代码在支撑着它,以使得该程序能够正常运行。这套代码至少包括入口函数、以及其所依赖的函数构成的函数集合。当然,它还包含了各种标准库函数的实现。

这个“支撑模块”就叫做运行时库(Runtime Library)。而C语言的运行库,即被称为C运行时库(CRT)。

CRT大致包括:启动与退出相关的代码(包括入口函数及入口函数所依赖的其他函数)、标准库函数(ANSI C标准规定的函数实现)、I/O相关、堆的封装实现、语言特殊功能的实现以及调试相关。

其中标准库函数的实现占据了主要地位。标准库函数大家想必很熟悉了,而我们平时常用的printf,scanf函数就是标准库函数的成员。

C语言标准库在不同的平台上实现了不同的版本,我们只要依赖其接口定义,就能保证程序在不同平台上的一致行为。C语言标准库有24个,囊括标准输入输出、文件操作、字符串操作、数学函数以及日期等等内容。大家有兴趣的可以自行搜索。

既然C语言提供了标准库函数供我们使用,那么以什么形式提供呢?源代码吗?当然不是了。

下面我们引入静态链接库的概念。我们几乎每一次写程序都难免去使用库函数,那么每一次去编译岂不是太麻烦了。干嘛不把标准库函数提前编译好,需要的时候直接链接呢?我很负责任的说,我们就是这么做的。

那么,标准库以什么形式存在呢?一个目标文件?我们知道,链接的最小单位就是一个个目标文件,如果我们只用到一个printf函数,就需要和整个库链接的话岂不是太浪费资源了么?但是,如果把库函数分别定义在彼此独立的代码文件里,这样编译出来的可是一大堆目标文件,有点混乱吧?所以,编辑器系统提供了一种机制,将所有的编译出来的目标文件打包成一个单独的文件,叫做静态库(static library)。

当链接器和静态库链接的时候,链接器会从这个打包的文件中“解压缩”出需要的部分目标文件进行链接。这样就解决了资源浪费的问题。

Linux/Unix系统下ANSI C的库名叫做libc.a,另外数学函数单独在libm.a库里。静态库采用一种称为存档(archive)的特殊文件格式来保存。其实就是一个目标文件的集合,文件头描述了每个成员目标文件的位置和大小。

光说不练是假把式,我们自己做个静态库试试。为了简单起见我们就做一个只有两个函数的私有库吧。

我们在swap.c里定义一个swap函数,在add.c里定义了一个add函数。

 1// swap.c
 2void swap(int *num1, int *num2){
 3    int tmp = *num1;
 4    *num1 = *num2;
 5    *num2 = tmp;
 6}
 7
 8// add.c
 9int add(int a, int b){
10    return a + b;
11}

最后还有含有它们声明的calc.h头文件:

 1// calc.h
 2
 3#ifndef CALC_H_
 4
 5#define CALC_H_
 6
 7#ifdef _cplusplus
 8
 9extern "C"
10{
11#endif
12
13void swap(int *, int *);
14
15
16int add(int, int);
17
18#ifdef _cplusplus
19
20}
21
22#endif
23
24#endif // CALC_H_

我们分别编译它们得到了swap.o和add.o这两个目标文件,最后使用ar命令将其打包为一个静态库。

640?wx_fmt=jpeg

现在我们怎么使用这个静态库呢?我们写一个test.c使用这个库中的swap函数吧。代码如下:

 1#include &lt;stdio.h&gt;
 2
 3#include &lt;stdlib.h&gt;
 4
 5#include "calc.h"
 6
 7int main(int argc, char *argv[])
 8{
 9
10    int a = 1, b = 2;
11
12    swap(&amp;a, &amp;b);
13
14    printf("%d %d\n", a, b);
15
16    return EXIT_SUCCESS;
17
18}

下来是编译执行,命令行执行gcc test.c ./libcalc.a -o test编译,执行。如图,我们输出了预期的结果。

640?wx_fmt=jpeg

可能你会问,我们使用C语言标准库的时候,编译并不需要加什么库名啊?

是的,我们不需要。因为标准库已经是标准了,所以会被默认链接。不过因为数学函数库libm.a没有默认链接,所以我们使用了数学函数的代码在编译时需要在命令行指定 -lm 链接(-l是制定链接库,m是去掉lib之后的库名),不过现在好多gcc都默认链接libm.c库了,比如我机子上的gcc 4.6.3会默认链接的。

正如我们所看到的,静态链接库解决了一些问题,但是它同时带来了另一些问题。比如说每一个使用了相同的C标准函数的程序都需要和相关目标文件进行链接,浪费磁盘空间;当一个程序有多个副本执行时,相同的库代码部分被载入内存,浪费内存;当库代码更新之后,使用这些库的函数必须全部重新编译……

有更好的办法吗?当然有。我们接下来引入动态链接库/共享库(shared library)。

动态链接库/共享库是一个目标模块,在运行时可以加载到任意的存储器地址,并和一个正在运行的程序链接起来。这个过程就是动态链接(dynamic linking),是由一个叫做动态链接器(dynamic linker)的程序完成的。

Unix/Linux中共享库的后缀名通常是.so(微软那个估计大家很熟悉,就是DLL文件)。怎么建立一个动态链接库呢?

我们还是以上面的代码为例,我们先删除之前的静态库和目标文件。首先是建立动态链接库,我们执行gcc swap.c add.c -shared -o libcalc.so 就可以了,就这么简单(微软那个有所区别,我们在这里只为说明概念,有兴趣的同学请自行搜索)。

640?wx_fmt=jpeg

如何使用呢?我们继续编译测试代码,执行gcc test.c -o test ./libcalc.so即可。运行后我们仍旧得到了预期的结果。

640?wx_fmt=jpeg

这看起来也没啥不一样的啊。其实不然,我们用ldd命令检查test文件的依赖。

640?wx_fmt=jpeg

我们看到这个文件能顺利运行需要依赖libcalc.so这个动态库,我们还能看到C语言的标准库默认也是动态链接的(在gcc编译的命令行加上 -static 可以要求静态链接)。

好处在哪?第一,库更新之后,只需要替换掉动态库文件即可,无需编译所有依赖库的可执行文件。第二,程序有多个副本执行时,内存中只需要一份库代码,节省空间。

大家想想,C语言标准库好多程序都在用,但内存只有一份代码,这样节省的空间很可观吧,而且假如库代码发现bug,只需要更新libc.so即可,所有程序即可使用新的代码,岂不是很Cool。

好了,关于库我们就说到这里了,再说下去就没法子结束了。

链接的具体过程

我们来看看链接过程中具体做的事情。链接的步骤大致包括了地址和空间分配(Address and Storage Allocation)、符号决议(Symbol Resolution)和重定位(Relocation)等主要步骤。

首先是地址和空间分配,我们之前提到的目标文件其实全称叫做可重定位目标文件(这只是一种翻译,叫法很多…)。

目标文件的格式已经无限度接近可执行文件了,Unix/Linux下的目标文件的格式叫做ELF(Executable and Linkable Format,可执行连接格式)。

详细的讨论可执行文件的格式超出了本文范围,我们只需要知道可执行文件中代码,数据,符号等内容分别存储在不同的段中就可以了,这也和保护模式下的内存分段是有一定关系的,但是这个又会扯远就不详谈了……

地址和空间分配以及重定位我们简单叙述一下就好,但是符号决议这里我想稍微展开描述一下。

什么是符号(symbol)?简单说我们在代码中定义的函数和变量可以统称为符号。符号名(symbol name)就是函数名和变量名了。

目标文件的拼合其实也就是对目标文件之间相互的符号引用的一个修正。我们知道一个C语言代码文件只要所有的符号被声明过就可以通过编译了,可是对某符号的引用怎么知道位置呢?比如我们调用了printf函数,编译时留下了要填入的函数地址,那么printf函数的实际地址在那呢?这个空位什么时候修正呢?当然是链接的时候,重定位那一步就是做这个的。但是在修改地址之前需要做符号决议,那什么是符号决议呢?正如前文所说,编译期间留下了很多需要重新定位的符号,所以目标文件中会有一块区域专门保存符号表。那链接器如何知道具体位置呢?其实链接器不知道,所以链接器会搜索全部的待链接的目标文件,寻找这个符号的位置,然后修正每一个符号的地址。

这时候我们可以隆重介绍一个几乎所有人在编译程序的时候会遇见的问题——符号查找问题。这个通常有两种错误形式,即找不到某符号或者符号重定义。

首先是找不到符号,比如,当我们声明了一个swap函数却没有定义它的时候,我们调用这个函数的代码可以通过编译,但是在链接期间却会遇到错误。形如“test.c:(.text+0x29): undefined reference to ‘swap’”这样,特别的,MSVC编译器报错是找不到符号_swap。

咦?那个下划线哪里来的?

这得从C语言刚诞生说起。当C语言刚面世的时候,已经存在不少用汇编语言写好的库了,因为链接器的符号唯一规则,假如该库中存在main函数,我们就不能在C代码中出现main函数了,因为会遭遇符号重定义错误,倘若放弃这些库又是一大损失。所以当时的编译器会对代码中的符号进行修饰(name decoration),C语言的代码会在符号前加下划线,fortran语言在符号前后都加下划线,这样各个目标文件就不会同名了,就解决了符号冲突的问题。

随着时间的流逝,操作系统和编译器都被重写了好多遍了,当前的这个问题已经可以无视了。所以新版的gcc一般不会再加下划线做符号修饰了(也可以在编译的命令行加上-fleading-underscore/-fno-fleading-underscore开打开/关闭这个是否加下划线)。而MSVC依旧保留了这个传统,所以我们可以看到_swap这样的修饰。

顺便说一下,符号冲突是很常见的事情,特别是在大型项目的开发中,所以我们需要一个约定良好的命名规则。C++也引入了命名空间来帮助我们解决这些问题,因为C++中存在函数重载这些东西,所以C++的符号修饰更加复杂难懂(Linux下有c++filt命令帮助我们翻译一个被C++编译器修饰过的符号)。

说了这么多,该到了我们变成中需要注意的一个大问题了。即存在同名符号时链接器如何处理。不是刚刚说了会报告重名错误吗?怎么又要研究这个?很可惜,不仅仅这么简单。

在编译时,编译器会向汇编器输出每个全局符号,分为强(strong)符号和弱符号(weak),汇编器把这个信息隐含的编码在可重定位目标文件的符号表里。其中函数和已初始化过的全局变量是强符号,未初始化的全局变量是弱符号。根据强弱符号的定义,GNU链接器采用的规则如下:

不允许多个强符号

如果有一个强符号和一个或多个弱符号,则选择强符号

如果有多个弱符号,则随机选择一个

好了,就三条,第一条会报符号重名错误的,而后两条默认情况下甚至连警告都不会有。关键就在这里,默认甚至连警告都没有。

我们来个实验具体说一下:

 1// link1.c
 2
 3#include &lt;stdio.h&gt;
 4
 5int n;
 6
 7int main(int argc, char *argv[])
 8
 9{
10
11  printf("It is %dn", n);
12
13  return 0;
14
15}

1// link2.c
2
3int n = 5;

这两个文件编译运行会输出什么呢?聪明的你想必已经知道了吧?没错,就是5。

640?wx_fmt=jpeg

初始化过的n是强符号,被优先选择了。但是,在很复杂的项目代码,这样的错误很难发现,特别是多线程的……不过当我们怀疑代码中的bug可能是因为此原因引起的时候,我们可以在gcc命令行加上-fno-common这个参数,这样链接器在遇到多重定义的符号时,都会给出一条警告信息,而无关强弱符号。如图所示:

640?wx_fmt=jpeg

好了,到这里我们的下篇到此也该结束了,不过关于编译链接其实远比这深奥复杂的多,我权当抛砖引玉,各位看官自可深入研究。

相关阅读:

操作系统是个大骗子?

一个翻译家族的发家史

(完)

你看到的只是冰山一角, 更多精彩文章,请移步《2016文章精华》或者《2017文章精华》


码农翻身

用故事讲述技术

640?wx_fmt=jpeg

gcc/g++ 链接库的编译与链接 gcc/g++ 链接库的编译与链接 surgewong@gmail.com http://blog.youkuaiyun.com/surgewong       程序编译一般需要经预处理、编译、汇编和链接几个步骤。在实际应用中,有些公共代码需要反复使用,就把这些代码编译成为“库”文件。在链接步骤中,连接器将从库文件取得所需的代码,复制到生成的可执行文件中,这种库称为静态(链接)库,其特点是可 阅读详情

相关推荐

预处理、编译、汇编和链接

说明 C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码)。流程包括:预处理、编译、汇编和链接。hello.c文件为一个自己写好的包含main函数的c文件。 预处理 第一个阶段是预处理阶段,在正式的编译阶段之前进行。预处理阶段将根据已放置在文件中的预处理指令来修改源文件的内容。如#include指令就是一个预处理指令,它把头文件的内容添加到.cpp文件中...

Zhubingge的博客 9303

《程序员的自我修养》-编译和链接

一个.c文件最终成为a.out文件需要经历预处理,编译,汇编,链接这4个过程。gcc总过程如下预编译解释 主要处理源代码文件里以#开始的预编译指令。eg.#include,#define 内容 1.展开所有的宏定义,#define 2.处理所有条件编译指令,#if, #,ifdef,#elif,#else,#endif 3.处理#include,通过递归过程

zhuziyu1157817544 1137

C语言编译与链接

通过符号将多个模块组合成一个独立程序的过程即。

m0_75104457的博客 1943

每个程序员都应当知道的编译器优化知识

原文见链接每个程序员都应当知道的编译器优化知识

黎明前的沉睡 256

程序员的自我修养之编译和链接

一个程序的源代码到变成可执行程序,要经过 --预处理—编译—汇编–链接四个过程。 1. 预处理都做了啥? 指令: gcc -E hello.c -o hello.i 预处理主要处理源代码中以“#”开头的预编译指令,主要包括: 将所有的“#define”删除并展开所有的宏定义。 处理所有条件预编译指令,比如“#if”"#ifdef""#else"“endif” 处理#include预编译指令...

weixin_44027897的博客 654

SAP 资产主数据屏幕增强:对比字段追加与子屏幕/页签 2 种方案优劣

本文深入对比了SAP资产主数据(AS01/AS03)屏幕增强的两种方案:字段追加与子屏幕/页签。从技术实现、业务适配、系统维护等维度分析各自优劣,提供方案选型决策树和混合方案实践建议,帮助企业在SAP实施中做出最优选择。

weixin_28559803的博客 285

符号修饰与函数签名

约在20世纪70年代以前,编译器编译源代码产生目标文件时,符号名与相应的变量和函数的名字是一样的。比如一个汇编源代码里面包含了一个函数foo,那么汇编器将它编译成目标文件以后,foo在目标文件中的相对应的符号名也是foo。当后来UNIX平台和C语言发明时,已经存在了相当多的使用汇编编写的库和目标文件。这样就产生了一个问题,那就是如果一个C程序要使用这些库的话,C语言中不可以使用这些库中定义的函数和...

weixin_33978044的博客 657

保姆级教程:用ROS2 Humble和激光雷达,5分钟搞定机器人避障(附Python代码)

本文提供了一份详细的ROS2 Humble激光雷达避障实战指南,帮助开发者快速实现机器人避障功能。通过环境配置、硬件连接和Python代码实现,5分钟内即可让机器人自主避开障碍物。文章还涵盖了参数调优、常见问题排查及进阶优化技巧,适合机器人开发初学者和进阶用户。

weixin_42528902的博客 343

【程序员的自我修养】[动态图文] 理解编译到链接的过程 [编译与链接一]

程序的编译及链接 在学习C语言的过程中,每一个 .c 文件都会经过编译链接等预处理操作才能成为一个 .exe 的可执行文件 这些从 .c 源文件到 .exe 文件的大致变化过程的,就是本篇文章的内容

七月.cc的博客 5635

Memory SystemsCache, DRAM, Disk翻译学习DRAM部分(二)

DRAM 的发展史

qq_33473931的博客 996

调节桌面图标大小的方法(编程)

如果你对其他操作系统或编程语言有类似的需求,可以根据具体的情况进行相应的调整和搜索相关的库和方法。在编程中,我们可以使用各种桌面应用程序来调节桌面图标的大小。库提供的函数,我们获取了桌面图标的窗口句柄,并使用Windows消息发送方式设置图标的大小。你可以根据自己的需求修改代码中的图标大小值,并在运行代码后观察桌面图标的变化。函数并传入所需的图标大小,这里设置为48x48像素。首先,我们需要使用Python的第三现的方法。首先,我们需要使用Python的第三方库。首先,我们需要使用实现的方法。

CyberFlare的博客 439

什么是编译和链接(通俗易懂,深入本质)

我们平时所说的程序,是指双击后就可以直接运行的程序,这样的程序被称为可执行程序(Executable program)。

qq_64844180的博客 1万+

高斯过程的数学理解

G澳大利亚的过程是有益的,特别是当我们有少量数据时。当我在制造业担任数据科学家时,我们的团队使用这种算法来揭示我们接下来应该进行哪些实验条件。但是,此算法不如其他算法流行。在这篇博客中,我将通过可视化和 Python 实现来解释高斯过程 [1] 的数学背景。

gongdiwudu的专栏 3611

编译和链接

在C语言标准(ANSI C)的任何一种实现中,都存在两个不同的环境:翻译环境和执行环境。翻译环境:负责将该环境中的源代码转换为可执行的机器指令(二进制指令)。执行环境:负责实际执行代码。而翻译环境所作的事,就主要是进行编译和链接。编译:处理项目中的各个源文件(.c),并生成对应的目标文件(.obj)。链接:建立各个目标文件之间的联系。其中,编译还可被分为三个过程:预处理(预编译),编译,汇编。

2302_80372340的博客 1905

【2026年华为暑期实习-非AI方向(通软&嵌软&测试&算法&数据科学)- 7月15日-第二题- 手机多载波冲突选择】(题目+思路+Java&C++&Python解析+在线测试)

摘要 本文提出了一种解决手机基站载波选择问题的回溯算法。给定N个载波,每个载波具有带宽和所属频段,要求选择最多K个载波,满足:1) 不选择同一频段的多个载波;2) 不选择冲突频段的载波;3) 总带宽最大化。算法通过预处理冲突关系,使用位掩码优化冲突检查,按编号顺序回溯枚举所有可能组合。当找到更大带宽或相同带宽但字典序更小的组合时更新答案。该方法在N≤25和K≤8的约束下高效可行,并通过样例验证了正确性。输出为最大总带宽和对应的最优载波编号序列。

m0_73659489的博客 74

偏最小二乘回归分析原理详解和案例分析实例

偏最小二乘回归分析原理详解背景偏最小二乘回归分析 Partial least squares regression analysis基本思想建模步骤步骤一:分别提取两变量组的第一对成分,并使之相关性达最大步骤二:建立 y1,⋯ ,yp\boldsymbol{y}_{1}, \cdots, \boldsymbol{y}_{p}y1​,⋯,yp​ 对 u1\boldsymbol{u}_{1}u1​ 的回归及 x1,⋯ ,xm\boldsymbol{x}_{1}, \cdots, \boldsymbol{x}_{

从事脑科学核磁共振方法学研究,在Nature communications等权威期刊发表研究论文,熟练掌握磁共振处理方法和统计学方法,欢迎大家和我交流。 9977

程序员的自我修养--链接、装载与库笔记:编译和链接

集成开发环境(IDE),如Visual Studio,一般都将编译和链接的过程一步完成,通常将这种编译和链接合并到一起的过程称为构建(Build)。 1. 被隐藏了的过程 以下是测试代码hello.c: #include <stdio.h> int main() { printf("Hello World\n"); return 0; } 在Linux下执行以下命令:将...

网络资源是无限的 7534

BW 技术 InfoObject 初始化,一次把 0LANGU、0CURRENCY、0IOBJNM 这些底座对象讲透

摘要:BW/4HANA和S/4HANA嵌入式分析中最容易被忽视的关键技术InfoObject(如0LANGU、0UNIT等)是系统运行的基础骨架。这些对象虽不直接涉及业务,但若未正确激活会导致多维分析、查询运行等核心功能异常。BW/4HANA中应通过任务清单SAP_BW4_SETUP_SIMPLE或SAP_BW4_SET_OPERATION_SCOPE激活,而非旧事务码RSTCO_ADMIN。S/4HANA嵌入式分析仍需依赖这些底层BW组件,配置需参考SAP Note 2636754。项目实践中需特别关注这

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1390

程序的编译和链接

在ANSIC的任何一种实现中,存在两个不同的环境1.第一种是翻译环境,在这个环境中源代码被转换为可执行的机器指令2.第二种是执行环境,它用于实际的执行代码。

qq_73874574的博客 1712

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

【程序员的自我修养】2. 编译和链接

以"hello world"程序为例子,整个过程可以用如下的图表示。构建过程分为4个过程:预处理、编译、汇编、链接。

GouDanAndOcean的博客 871

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

上一篇: 白话TCP拥塞控制:运粮的河道堵塞了怎么办?
下一篇: 哪些人创业更可能成功?
码农翻身
码农翻身 《码农翻身》作者 《码农翻身》作者
博客等级 码龄8年 3万+粉丝 1084原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值