Compiling C
出自Ubuntu中文
|
[编辑] C 编程中相关文件后缀
.a | 静态库 (archive) |
.c | C源代码(需要编译预处理) |
.h | C源代码头文件 |
.i | C源代码(不需编译预处理) |
.o | 对象文件 |
.s | 汇编语言代码 |
.so | 动态库 |
[编辑] 单个源文件生成可执行程序
下面是一个简单的“hello, ubuntu”程序的源代码:
/* helloubuntu.c */
#include <stdio.h>
int main(int argc,char *argv[])
{
printf("hello, ubuntu\n");
return 0;
}
最简单直接的编译该代码为可执行程序的方法是,将该代码保存为文件 helloubuntu.c,并执行以下命令:
$ gcc -Wall helloubuntu.c编译器通过检查命令行中指定的文件的后缀名可识别其为 C 源代码文件。GCC 默认的动作:编译源代码文件生成对象文件(object file),链接对象文件得到可执行程序,删除对象文件。由于命令行中未指定可执行程序的文件名,编译器采用默认的 a.out。在命令行中输入程序名可使其执行并显示结果:
$ ./a.outhello, ubuntu
选项 -o 用来指定所生成的可执行程序的文件名。下面的命令生成名为 helloubuntu 的可执行程序:
$ gcc -Wall helloubuntu.c -o helloubuntu在命令行中输入程序名将使其执行,如下:
$ ./helloubuntuhello, ubuntu
[编辑] 源文件生成对象文件
选项 -c 指示 GCC 编译源代码文件,但将对象文件保留在磁盘中并跳过链接对象文件生成可执行文件这一步。在这种情况下,默认的输出文件的文件名同源代码文件名一致,只不过後缀换为 .o 。例如:下面的命令将生成名为 helloubuntu.o 的对象文件:
$ gcc -c -Wall helloubuntu.c选项 -o 可用来指定生成的对象文件的文件名。以下命令将产生名为kubuntu.o的对象文件:
$ gcc -c -Wall helloubuntu.c -o kubuntu.o当构建对象库或者生成一系列对象文件以备稍後链接用时,一条命令即可从多个源码文件生成对应的对象文件。下面的命令将生成对象文件ubuntu.o, kubuntu.o 与 xubuntu.o:
$ gcc -c -Wall ubuntu.c kubuntu.c xubuntu.c
[编辑] 多个源文件生成可执行程序
即使多个源码文件被编译,GCC编译器也会自动进行链接操作。例如:下面的代码保存在名为 hellomain.c 的文件中并调用一个名为 sayhello()的函数:
/* hellomain.c */
void sayhello(void);
int main(int argc,char *argv[])
{
sayhello();
return 0;
}
以下代码保存在名为 sayhello.c 的文件中并定义了 sayhello() 函数:
/* sayhello.c */
#include <stdio.h>
void sayhello()
{
printf("hello, ubuntu\n");/*这里有个小错误,是中文输入法造成的引号使gcc报错*/
}
下面的命令将两个文件分别编译为对象文件且将其链接为可执行程序 hello,并删除对象文件:
$ gcc -Wall hellomain.c sayhello.c -o hello
[编辑] 编译预处理
选项 -E 指示编译器只进行编译预处理。下面的命令将预处理源码文件 helloubuntu.c 并将结果在标准输出中列出:
$ gcc -E helloubuntu.c选项 -o 用来将预处理过的代码定向到一个文件。像本文一开始给出的後缀列表所给出的,不需经过预处理的C源码文件保存为後缀为 .i的文件中,这种文件可以这样来获得:
$ gcc -E helloubuntu.c -o helloubuntu.i
[编辑] 生成汇编代码
选项 -S 指示编译器生成汇编语言代码然後结束。下面的命令将由 C 源码文件 helloubuntu.c 生成汇编语言文件 helloubuntu.s:
$ gcc -S helloubuntu.c汇编语言的形式依赖于编译器的目标平台。如果多个源码文件被编译,每个文件将分别产生对应的汇编代码模块。
[编辑] 创建静态库
静态库是编译器生成的普通的 .o 文件的集合。链接一个程序时用库中的对象文件还是目录中的对象文件都是一样的。静态库的另一个名字叫归档文件(archive),管理这种归档文件的工具叫 ar 。
要构建一个库,首先要编译出库中需要的对象模块。例如,下面的两个源码文件为 hellofirst.c 和 hellosecond.c:
/* hellofirst.c */
#include <stdio.h>
void hellofirst()
{
printf(“The first hello\n”);
}
/* hellosecond.c */
#include <stdio.h>
void hellosecond()
{
printf(“The second hello\n”);
}
这两个源码文件可以用以下命令编译成对象文件:
$ gcc -c -Wall hellofirst.c hellosecond.c程序 ar 配合参数 -r 可以创建一个新库并将对象文件插入。如果库不存在的话,参数 -r 将创建一个新的,并将对象模块添加(如有必要,通过替换)到归档文件中。下面的命令将创建一个包含本例中两个对象模块的名为 libhello.a 的静态库:
$ ar -r libhello.a hellofirst.o hellosecond.o现在库已经构建完成可以使用了。下面的程序 twohellos.c 将调用该库中的这两个函数:
/* twohellos.c */
void hellofirst(void);
void hellosecond(void);
int main(int argc,char *argv[])
{
hellofirst();
hellosecond();
return 0;
}
程序 twohellos 可以通过在命令行中指定库用一条命令来编译和链接,命令如下:
$ gcc -Wall twohellos.c libhello.a -o twohellos静态库的命名惯例是名字以三个字母 lib 开头并以後缀 .a 结束。所有的系统库都采用这种命名惯例,并且它允许通过 -l(ell) 选项来简写命令行中的库名。下面的命令与先前命令的区别仅在于 gcc 期望的找寻该库的位置不同:
$ gcc -Wall twohellos.c -lhello -o twohellos指定完整的路径名可使编译器在给定的目录中寻找库。库名可以指定为绝对路径(比如 /usr/worklibs/libhello.a)或者相对与当前目录的路径(比如 ./lib/libhello.a)。选项 -l 不能具有指定路径的能力,但是它要求编译器在系统库目录下找寻该库。
[编辑] 创建共享库
共享库是编译器以一种特殊的方式生成的对象文件的集合。对象文件模块中所有地址(变量引用或函数调用)都是相对而不是绝对的,这使得共享模块可以在程序的运行过程中被动态地调用和执行。
要构建一个共享库,首先要编译出库中需要的对象模块。例如:下面是文件名为 shellofirst.c 和 shellosecond.c 的两个源码文件:
/* shellofirst.c */
#include <stdio.h>
void shellofirst()
{
printf(“The first hello from a shared library\n”);
}
/* shellosecond.c */
#include <stdio.h>
void shellosecond()
{
printf(“The second hello from a shared library\n”);
}
要将以上两个源码文件编译成对象文件,可以用下面的命令:
$ gcc -c -Wall -fpic shellofirst.c shellosecond.c选项 -c 告诉编译器只生成 .o 的对象文件。选项 -fpic 使生成的对象模块采用浮动的(可重定位的)地址。缩微词 pic 代表“位置无关代码”(position independent code)。
下面的 gcc 命令将对象文件构建成一个名为 hello.so 的共享库:
$ gcc -Wall -shared shellofirst.o shellosecond.o -o hello.so选项 -o 用来为输出文件命名,而文件後缀名 .so 告诉编译器将对象文件链接成一个共享库。通常情况下,链接器定位并使用 main() 函数作为程序的入口,但是本例中输出模块中没有这种入口点,为抑制错误选项 -shared 是必须的。
编译器能将後缀为 .c 的文件识别为 C 语言源代码文件,并知道如何将其编译成为对象文件。基于这一点,先前的两条命令我们可以合并为一条;下面的命令直接将模块编译并存储为共享库:
$ gcc -Wall -fpic -shared shellofirst.c shellosecond.c -o hello.so下面的程序,存储在文件 stwohellos.c 内,是调用共享库中两个函数的主程序:
/* stwohellos.c */
void shellofirst(void);
void shellosecond(void);
int main(int argc,char *argv[])
{
shellofirst();
shellosecond();
==========================================
return 0;
}
该程序可以用下面的命令编译并链接共享库:
$ gcc -Wall stwohellos.c hello.so -o stwohellos程序 stwohello 已经完成,但要运行它必须让其能定位到共享库 hello.so,因为库中的函数要在程序运行时被加载。 需要注意的是,当前工作目录可能不在共享库的查找路径中,因此需要使用如下的命令行设定环境变量LD_LIBRARY_PATH:
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./
[编辑] 超越命名惯例
如果环境要求你使用 .c 以外的後缀名来命名你的 C 源码文件,你可以通过 -x 选项来指定其对应的语言以忽略我们的命名规范。例如,下面的命令将从文件 helloworrld.jxj 编译 C 语言源代码并生成可执行文件 helloubuntu:
$ gcc -xc helloubuntu.jxj -o helloubuntu通常,在没有 -x 选项的情况下,任何具有未知後缀名的源码文件名都被认为是连接器可以识别的选项,并在不做任何更改的情况下传递给链接器。选项 -x 对其後的所有未知後缀的文件都起作用。例如,下面的命令使 gcc 将 align.zzz 和 types.xxx 都作为 C 源码文件来处理:
$ gcc -c -xc align.zzz types.xxxCompiling Cpp
出自Ubuntu中文
|
[编辑] C++ 编程中相关文件后缀
.a | 静态库 (archive) |
.C .c .cc .cp .cpp .cxx .c++ | C++源代码(需要编译预处理) |
.h | C或者C++源代码头文件 |
.ii | C++源代码(不需编译预处理) |
.o | 对象文件 |
.s | 汇编语言代码 |
.so | 动态库 |
<none> | 标准C++系统头文件 |
[编辑] 单个源文件生成可执行程序
下面是一个保存在文件 helloworld.cpp 中一个简单的 C++ 程序的代码:
/* helloworld.cpp */
#include <iostream>
int main(int argc,char *argv[])
{
std::cout << "hello, world" << std::endl;
return(0);
}
程序使用定义在头文件 iostream 中的 cout,向标准输出写入一个简单的字符串。该代码可用以下命令编译为可执行文件:
$ g++ helloworld.cpp
编译器 g++ 通过检查命令行中指定的文件的后缀名可识别其为 C++ 源代码文件。编译器默认的动作:编译源代码文件生成对象文件(object file),链接对象文件和 libstdc++ 库中的函数得到可执行程序。然后删除对象文件。由于命令行中未指定可执行程序的文件名,编译器采用默认的 a.out。程序可以这样来运行:
$ ./a.out hello, world
更普遍的做法是通过 -o 选项指定可执行程序的文件名。下面的命令将产生名为 helloworld 的可执行文件:
$ g++ helloworld.cpp -o helloworld
在命令行中输入程序名可使之运行:
$ ./helloworld hello, world
程序 g++ 是将 gcc 默认语言设为 C++ 的一个特殊的版本,链接时它自动使用 C++ 标准库而不用 C 标准库。通过遵循源码的命名规范并指定对应库的名字,用 gcc 来编译链接 C++ 程序是可行的,如下例所示:
$ gcc helloworld.cpp -lstdc++ -o helloworld
选项 -l (ell) 通过添加前缀 lib 和后缀 .a 将跟随它的名字变换为库的名字 libstdc++.a。而后它在标准库路径中查找该库。gcc 的编译过程和输出文件与 g++ 是完全相同的。
在大多数系统中,GCC 安装时会安装一名为 c++ 的程序。如果被安装,它和 g++ 是等同,如下例所示,用法也一致:
$ c++ helloworld.cpp -o helloworld
[编辑] 多个源文件生成可执行程序
如果多于一个的源码文件在 g++ 命令中指定,它们都将被编译并被链接成一个单一的可执行文件。下面是一个名为 speak.h 的头文件;它包含一个仅含有一个函数的类的定义:
/* speak.h */
#include <iostream>
class Speak
{
public:
void sayHello(const char *);
};
下面列出的是文件 speak.cpp 的内容:包含 sayHello() 函数的函数体:
/* speak.cpp */
#include "speak.h"
void Speak::sayHello(const char *str)
{
std::cout << "Hello " << str << "\n";
}
文件 hellospeak.cpp 内是一个使用 Speak 类的程序:
/* hellospeak.cpp */
#include "speak.h"
int main(int argc,char *argv[])
{
Speak speak;
speak.sayHello("world");
return(0);
}
下面这条命令将上述两个源码文件编译链接成一个单一的可执行程序:
$ g++ hellospeak.cpp speak.cpp -o hellospeak
PS:这里说一下为什么在命令中没有提到“speak.h“该文件(原因是:在“speak.cpp“中包含 有”#include"speak.h"“这句代码,它的意思是搜索系统头文件目录之前将先在当前目录中搜索文件“speak.h“。而”speak.h “正在该目录中,不用再在命令中指定了)。
[编辑] 源文件生成对象文件
选项 -c 用来告诉编译器编译源代码但不要执行链接,输出结果为对象文件。文件默认名与源码文件名相同,只是将其后缀变为 .o。例如,下面的命令将编译源码文件 hellospeak.cpp 并生成对象文件 hellospeak.o:
$ g++ -c hellospeak.cpp
命令 g++ 也能识别 .o 文件并将其作为输入文件传递给链接器。下列命令将编译源码文件为对象文件并将其链接成单一的可执行程序:
$ g++ -c hellospeak.cpp $ g++ -c speak.cpp $ g++ hellospeak.o speak.o -o hellospeak
选项 -o 不仅仅能用来命名可执行文件。它也用来命名编译器输出的其他文件。例如:除了中间的对象文件有不同的名字外,下列命令生将生成和上面完全相同的可执行文件:
$ g++ -c hellospeak.cpp -o hspk1.o $ g++ -c speak.cpp -o hspk2.o $ g++ hspk1.o hspk2.o -o hellospeak
[编辑] 编译预处理
选项 -E 使 g++ 将源代码用编译预处理器处理后不再执行其他动作。下面的命令预处理源码文件 helloworld.cpp 并将结果显示在标准输出中:
$ g++ -E helloworld.cpp
本文前面所列出的 helloworld.cpp 的源代码,仅仅有六行,而且该程序除了显示一行文字外什么都不做,但是,预处理后的版本将超过 1200 行。这主要是因为头文件 iostream 被包含进来,而且它又包含了其他的头文件,除此之外,还有若干个处理输入和输出的类的定义。
预处理过的文件的 GCC 后缀为 .ii,它可以通过 -o 选项来生成,例如:
$ gcc -E helloworld.cpp -o helloworld.ii
[编辑] 生成汇编代码
选项 -S 指示编译器将程序编译成汇编语言,输出汇编语言代码而後结束。下面的命令将由 C++ 源码文件生成汇编语言文件 helloworld.s:
$ g++ -S helloworld.cpp
生成的汇编语言依赖于编译器的目标平台。
[编辑] 创建静态库
静态库是编译器生成的一系列对象文件的集合。链接一个程序时用库中的对象文件还是目录中的对象文件都是一样的。库中的成员包括普通函数,类定义,类的对象实例等等。静态库的另一个名字叫归档文件(archive),管理这种归档文件的工具叫 ar 。
在下面的例子中,我们先创建两个对象模块,然后用其生成静态库。
头文件 say.h 包含函数 sayHello() 的原型和类 Say 的定义:
/* say.h */
#include <iostream>
void sayhello(void);
class Say {
private:
char *string;
public:
Say(char *str)
{
string = str;
}
void sayThis(const char *str)
{
std::cout << str << " from a static library\n";
}
void sayString(void);
};
下面是文件 say.cpp 是我们要加入到静态库中的两个对象文件之一的源码。它包含 Say 类中 sayString() 函数的定义体;类 Say 的一个实例 librarysay 的声明也包含在内:
/* say.cpp */
#include "say.h"
void Say::sayString()
{
std::cout << string << "\n";
}
Say librarysay("Library instance of Say");
源码文件 syshello.cpp 是我们要加入到静态库中的第二个对象文件的源码。它包含函数 sayhello() 的定义:
/* sayhello.cpp */
#include "say.h"
void sayhello()
{
std::cout << "hello from a static library\n";
}
下面的命令序列将源码文件编译成对象文件,命令 ar 将其存进库中: $ g++ -c sayhello.cpp $ g++ -c say.cpp $ ar -r libsay.a sayhello.o say.o
程序 ar 配合参数 -r 创建一个新库 libsay.a 并将命令行中列出的对象文件插入。采用这种方法,如果库不存在的话,参数 -r 将创建一个新的库,而如果库存在的话,将用新的模块替换原来的模块。
下面是主程序 saymain.cpp,它调用库 libsay.a 中的代码:
/* saymain.cpp */
#include "say.h"
int main(int argc,char *argv[])
{
extern Say librarysay;
Say localsay = Say("Local instance of Say");
sayhello();
librarysay.sayThis("howdy");
librarysay.sayString();
localsay.sayString();
return(0);
}
该程序可以下面的命令来编译和链接:
$ g++ saymain.cpp libsay.a -o saymain
程序运行时,产生以下输出:
hello from a static library howdy from a static library Library instance of Say Local instance of Say
Mix C Cpp
出自Ubuntu中文
目录[隐藏]
|
[编辑] C与C++混合编程
C++ 是在 C 语言的基础上发展起来的。在某种程度上,我们可将 C++ 看做 C 的一种扩展。在本质上,二者的数据类型和函数调用惯例都是一致的,因此 C 与 C++ 混合编译也是很自然的事情。
二者的区别仅在于编译后函数的名字不同──C 简单地使用函数名而不考虑参数的个数或类型,而 C++ 编译后的函数名则总是将参数类型列表作为其一部分。尽管如此,C++ 提供了特殊的机制来声明 C 函数,这意味着一个 C++ 程序可以直接声明和调用 C 函数。
[编辑] C++调用C函数
下面是 C++ 程序调用 C 函数 csayhello() 的一个例子。由于该函数在 C++ 程序内声明时使用了 extern "C",故调用可以直接进行:
/* cpp2c.cpp */
#include <iostream>
extern "C" void csayhello(char *str);
int main(int argc,char *argv[])
{
csayhello("Hello from cpp to c");
return(0);
}
C 函数不需任何特殊处理,其代码如下:
/* csayhello.c */
#include <stdio.h>
void csayhello(char *str)
{
printf("%s\n",str);
}
下面三条命令编译以上两个文件并将二者链接为一个可执行文件。由于 gcc 和 g++ 的灵活性使得存在很多方法来完成该任务,但这三条命令或许是最常用的:
$ g++ -c cpp2c.cpp -o cpp2c.o $ gcc -c csayhello.c -o csayhello.o $ gcc cpp2c.o csayhello.o -lstdc++ -o cpp2c
注意到,在最后链接的时候指定 C++ 标准库是必须的,这是因为我们用的是 gcc 而不是 g++ 调用的链接器。如果使用的是 g++ 的话,C++ 标准库默认会被链接。
最普遍的做法是,将函数声明放到头文件中,然后将所有内容包含在 extern "C" 声明块内。文件内容像下面所示:
extern "C" {
int mlimitav(int lowend, int highend);
void updatedesc(char *newdesc);
double getpct(char *name);
};
[编辑] C调用C++函数
要使 C 程序能够调用 C++ 中函数的话,C++ 提供一个符合 C 调用惯例的函数是必须的。下面的例子演示了在 C++ 内创建 C 函数的语法:
/* cppsayhello.cpp */
#include <iostream>
extern "C" void cppsayhello(char *str);
void cppsayhello(char *str)
{
std::cout << str << "\n";
}
尽管函数 cppsayhello() 通过 extern "C" 声明为 C 函数,事实上它是 C++ 源代码的一部分,这意味着函数体内是真正的 C++ 代码。在函数内你可以自由地创建和析构对象。如果你要在 cppsayhello() 内调用 C 函数的话,将其声明为 extern "C" 是必须的。否则,编译器会将作为一个 C++ 函数并相应地更改函数名。
下面是调用 C++ 函数 cppsayhello() 的 C 程序:
/* c2cpp.c */
int main(int argc,char *argv[])
{
cppsayhello("Hello from C to C++");
return(0);
}
下面的命令编译并链接生成c2cpp:
$ g++ -c cppsayhello.cpp -o cppsayhello.o $ gcc -c c2cpp.c -o c2cpp.o $ gcc cppsayhello.o c2cpp.o -lstdc++ -o c2cpp