之前一直都是在Windows下享受IDE带来的各种便利,但对于程序到底是怎么从源代码变成可执行文件的,一直不甚明了。听了舒大师的Linux入门课后,研究一下在Linux下如何进行C语言程序开发。
主要从三个方面来展开:
- GCC的基本使用
- 静态库和动态库的编译和链接
- Makefile的编写
1. 任务概述
为了涵盖前面提出的三个主题,设置了如下的任务:
在4个c文件中分别实现4个函数,将其中两个文件编译为静态链接库,另两个文件编译为动态链接库,最后在主程序中分别调用这4个函数进行输出。
文件目录定义:
./bin //生成的可执行文件
./inc //头文件
func.h
./lib //生成的库文件
./obj //中间目标文件
./src //源文件
func_d1.c
func_d2.c
func_s1.c
func_s2.c
main.c
Makefile
2. GCC基本使用
不考虑库文件和makefile的使用,直接用GCC进行编译。
生成中间目标文件:
$ gcc -c src/func_d1.c
$ gcc -c src/func_d2.c
$ gcc -c src/func_s1.c
$ gcc -c src/func_s2.c
$ gcc -c src/main.c -I inc
链接文件:
$ gcc -o main func_d1.o func_d2.o func_s1.o func_s2.o main.o
结果测试:
$ ./main
结果输出:
hello world!
Using the static library.
Input number is: 1.
Using the dynamic library.
Input string is: hello.
当然,这里还并没有static library和dynamic library什么事,只是单纯地输出一些字符串而已。
3. 静态库和动态库
先删除之前生成的可执行文件,重新采用库文件的方式来实现。
$ rm main
3.1 静态链接库
将func_s1.c
和func_s2.c
这两个文件编译为静态链接库,生成libfunc_s.a
。
$ ar rc libfunc_s.a func_s1.o func_s2.o
3.2 动态链接库
将func_d1.c
和func_d2.c
这两个文件编译为动态链接库,生成libfunc_d.so
。
其中,需要以-fpic
方式,重新编译func_d1.o
和func_d2.o
,使其适应共享库的调用方式。
$ gcc -c -fpic src/func_d1.c src/func_d2.c
$ gcc -shared func_d1.o func_d2.o -o libfunc_d.so
3.3 程序调用
同时调用这两个库文件,生成最终的可执行文件。
gcc src/main.c -I inc -L . -lfunc_s -Wl,-rpath lib -lfunc_d -o main
执行生成的main
文件,结果与刚才相同,但此时才是真正地分别通过静态库和动态库的方式来实现函数调用的。
4. Makefile编写
通过编写Makefile文件,可以实现程序的自动化编译和链接,并且对于大型的程序,能够将文件组织得更加合理和有序。
Makefile文件如下所示:
SHELL = /bin/bash
CC = gcc
AR = ar rc
RM = rm -rf
MV = mv
MK = mkdir -p
INC_DIR = ./inc/
SRC_DIR = ./src/
OBJ_DIR = ./obj/
LIB_DIR = ./lib/
BIN_DIR = ./bin/
BIN_FILE = $(BIN_DIR)main
INC = $(INC_DIR)func.h
SRC_M = $(SRC_DIR)main.c
SRC_S = $(SRC_DIR)func_s1.c $(SRC_DIR)func_s2.c
SRC_D = $(SRC_DIR)func_d1.c $(SRC_DIR)func_d2.c
OBJ_M = main.o
OBJ_S = func_s1.o func_s2.o
OBJ_D = func_d1.o func_d2.o
LIB_S = $(LIB_DIR)libfunc_s.a
LIB_D = $(LIB_DIR)libfunc_d.so
all: init $(LIB_S) $(LIB_D) $(BIN_FILE)
init:
$(MK) $(OBJ_DIR)
$(MK) $(LIB_DIR)
$(MK) $(BIN_DIR)
$(LIB_S): $(OBJ_S)
$(AR) $(LIB_S) $(OBJ_S)
$(MV) $(OBJ_S) $(OBJ_DIR)
$(OBJ_S): $(SRC_S)
$(CC) -c $(SRC_S)
$(LIB_D): $(OBJ_D)
$(CC) -shared $(OBJ_D) -o $(LIB_D)
$(MV) $(OBJ_D) $(OBJ_DIR)
$(OBJ_D): $(SRC_D)
$(CC) -c -fpic $(SRC_D)
$(BIN_FILE): $(OBJ_M)
$(CC) $(OBJ_M) $(LIB_S) $(LIB_D) -o $(BIN_FILE)
$(MV) $(OBJ_M) $(OBJ_DIR)
$(OBJ_M): $(SRC_M)
$(CC) -c $(SRC_M) -I $(INC_DIR) -o $(OBJ_M)
clean:
$(RM) $(OBJ_DIR) $(LIB_DIR) $(BIN_DIR)
$(RM) *.o
其中:
- 执行
make clean
命令清除之前的编译结果。 - 执行
make
命令进行整个程序的编译链接。
结果测试:
$ bin/main
最终输出结果也和预想的一致。
至此,已经打通了在Linux下进行C语言程序开发的必要路径。
5. 附程序源文件
main.c
#include <stdio.h>
#include "func.h"
void main(void)
{
printf("hello world!\n");
func_s1();
func_s2(1);
func_d1();
func_d2("hello");
return;
}
func.h
void func_s1(void);
void func_s2(int a);
void func_d1(void);
void func_d2(char *s);
func_s1.c
#include <stdio.h>
void func_s1(void)
{
printf("Using the static library.\n");
return;
}
func_s2.c
#include <stdio.h>
void func_s2(int a)
{
printf("Input number is: %d.\n", a);
return;
}
func_d1.c
#include <stdio.h>
void func_d1(void)
{
printf("Using the dynamic library.\n");
return;
}
func_d2.c
#include <stdio.h>
void func_d2(char *s)
{
printf("Input string is: %s.\n", s);
return;
}