Linux C开发基础

GCC

gcc简介

  • GCC 原名为 GNU C语言编译器(GNU C Compiler)

  • GCC(GNU Compiler Collection,GNU编译器套件)是由 GNU 开发的编程语言 译器。GNU 编译器套件包括 C、C++、Objective-C、Java、Ada 和 Go 语言前 端,也包括了这些语言的库(如 libstdc++,libgcj等)

  • GCC 不仅支持 C 的许多“方言”,也可以区别不同的 C 语言标准;可以使用命令行 选项来控制编译器在翻译源代码时应该遵循哪个 C 标准。例如,当使用命令行参数 -std=c99 启动 GCC 时,编译器支持 C99 标准。

  • 安装命令 sudo apt install gcc g++ (版本 > 4.8.5)

  • 查看版本 gcc/g++ -v/–version

gcc工作流程

gcc和g++的区别

  • gcc 和 g++都是GNU(组织)的一个编译器。

  • 误区一:gcc 只能编译 c 代码,g++ 只能编译 c++ 代码。两者都可以,请注意: 后缀为 .c 的,gcc 把它当作是 C 程序,而 g++ 当作是 c++ 程序后缀为 .cpp 的,两者都会认为是 C++ 程序,C++ 的语法规则更加严谨一些。 编译阶段,g++ 会调用 gcc,对于 C++ 代码,两者是等价的,但是因为 gcc 命令不能自动和 C++ 程序使用的库联接,所以通常用 g++ 来完成链接,为了统 一起见,干脆编译/链接统统用 g++ 了,这就给人一种错觉,好像 cpp 程序只 能用 g++ 似的

  • 误区二:gcc 不会定义 __cplusplus 宏,而 g++ 会。 实际上,这个宏只是标志着编译器将会把代码按 C 还是 C++ 语法来解释 。 如上所述,如果后缀为 .c,并且采用 gcc 编译器,则该宏就是未定义的,否则, 就是已定义

  • 误区三:编译只能用 gcc,链接只能用 g++ 。 严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用 gcc/g++,而链接可以用 g++ 或者 gcc -lstdc++。 gcc 命令不能自动和C++程序使用的库联接,所以通常使用 g++ 来完成联接。 但在编译阶段,g++ 会自动调用 gcc,二者等价

gcc常用参数选项

gcc编译选项 说明
-E 预处理指定的源文件,不进行编译
-S 编译指定的源文件,但是不进行汇编
-c 编译、汇编指定的源文件,但是不进行链接
-o [file1] [file2] / [file2] -o [file1] 将文件 file2 编译成可执行文件 file1
-I directory 指定 include 包含文件的搜索目录
-g 在编译的时候,生成调试信息,该程序可以被调试器调试
-D 在程序编译的时候,指定一个宏
-w 不生成任何警告信息
-Wall 生成所有警告信息
-On 开启编译优化,-O0表 示没有优化,-O1为缺省值,-O3优化级别最高
-l 在程序编译的时候,指定使用的库
-L 指定编译的时候,搜索的库的路径。
-fPIC/fpic 生成与位置无关的代码
-shared 生成共享目标文件,通常用在建立共享库时
-std 指定C方言,如:-std=c99,gcc默认的方言是GNU C

静态库制作

命名规则

  • Linux : libxxx.a

    ​ lib : 前缀(固定)

    ​ xxx : 库的名字,自己起

    ​ .a : 后缀(固定)

  • Windows :

​ libxxx.lib

制作方法

  • gcc 获得 .o 文件

  • 将 .o 文件打包,使用 ar 工具(archive)

    ar rcs libxxx.a xxx.o xxx.o

    r – 将文件插入备存文件中

    c – 建立备存文件

    s – 索引

动态库制作

当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统的动态载入器来获取该绝对路径。对于elf格式的可执行程序,是由ld-linux.so来完成的,它先后搜索elf文件的 DT_RPATH段 ——> 环境变量 LD_LIBRARY_PATH ——> /etc/ld.so.cache文件列表 ——> /lib/,/usr/lib 目录找到库文件后将其载入内存。

命名规则

  • Linux : libxxx.so

    ​ lib : 前缀(固定)

    ​ xxx : 库的名字,自己起

    ​ .so : 后缀(固定)

    ​ 在Linux下是一个可执行文件

  • Windows :

libxxx.dll

制作方法

  • gcc 得到 .o 文件,得到和位置无关的代码

    gcc -c –fpic/-fPIC a.c b.c

  • gcc 得到动态库

    gcc -shared a.o b.o -o libcalc.so

Makefile

规则和工作原理

  • 一个 Makefile 文件中可以有一个或者多个规则

    目标 …: 依赖 …

    ​ 命令(Shell 命令) …

    • 目标:最终要生成的文件(伪目标除外)

    • 依赖:生成目标所需要的文件或是目标

    • 命令:通过执行命令对依赖操作生成目标(命令前必须 Tab 缩进)

  • Makefile 中的其它规则一般都是为第一条规则服务的。

  • 命令在执行之前,需要先检查规则中的依赖是否存在

    • 如果存在,执行命令
    • 如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的, 如果找到了,则执行该规则中的命令
  • 检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间

    • 如果依赖的时间比目标的时间晚,需要重新生成目标
    • 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被 执行

变量

  • 自定义变量

    变量名=变量值 var=hello $获取变量的值,$(变量名)

  • 预定义变量

    • AR : 归档维护程序的名称,默认值为 ar

    • CC : C 编译器的名称,默认值为 cc

    • CXX : C++ 编译器的名称,默认值为 g++

    • $@ : 目标的完整名称

    • $< : 第一个依赖文件的名称

    • $^ : 所有的依赖文件

示例

1
2
3
4
5
app:main.c a.c b.c 
gcc -c main.c a.c b.c

app:main.c a.c b.c
$(CC) -c $^ -o $@

模式匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
add.o:add.c
gcc -c add.c
div.o:div.c
gcc -c div.c
sub.o:sub.c
gcc -c sub.c
mult.o:mult.c
gcc -c mult.c
main.o:main.c
gcc -c main.c

# %.o:%.c \
- %: 通配符,匹配一个字符串 \
- 两个%匹配的是同一个字符串 \

%.o:%.c
gcc -c $< -o $@

函数

  • $(wildcard PATTERN…)

    • 功能:获取指定目录下指定类型的文件列表

    • 参数:PATTERN 指的是某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔

    • 返回:得到的若干个文件的文件列表,文件名之间使用空格间隔

    • 示例:

      ​ $(wildcard .c ./sub/.c)

      ​ 返回值格式: a.c b.c c.c d.c e.c f.c

  • $(patsubst <pattern>,<replacement>,<text>)

    • 功能:查找中<text>的单词(单词以空格”、“Tab”或“回车”“换行”分隔)是否符合<pattern>模式,如果匹配的话,则以<replacement>替换。

    • <pattern>可以包括通配符%,表示任意长度的字串。如果<replacement>中也包含%,那么,<replacement>中的这个%将是<pattern>中的那个%所代表的字串。(可以用\来转义,以\%来表示真实含义的%字符)

    • 返回:函数返回被替换过后的字符串

    • 示例:

      $(patsubst %.c, %.o, x.c bar.c)

      返回值格式: x.o bar.o

文件IO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// Linux系统IO函数
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int close(int fd);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
off_t lseek(int fd, off_t offset, int whence);
int stat(const char *pathname, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);

// stat结构体
struct stat {
dev_t st_dev; // 文件的设备编号
ino_t st_ino; // 节点
mode_t st_mode; // 文件的类型和存取的权限
nlink_t st_nlink; // 连到该文件的硬连接数目
uid_t st_uid; // 用户ID
gid_t st_gid; // 组ID
dev_t st_rdev; // 设备文件的设备编号
off_t st_size; // 文件字节数(文件大小)
blksize_t st_blksize; // 块大小
blkcnt_t st_blocks; // 块数
time_t st_atime; // 最后一次访问时间
time_t st_mtime; // 最后一次修改时间
time_t st_ctime; // 最后一次改变时间(指属性)
};

// 文件属性操作函数
int access(const char *pathname, int mode);
int chmod(const char *filename, int mode);
int chown(const char *path, uid_t owner, gid_t group);
int truncate(const char *path, off_t length);

// 目录操作函数
int rename(const char *oldpath, const char *newpath);
int chdir(const char *path);
char *getcwd(char *buf, size_t size);
int mkdir(const char *pathname, mode_t mode);
int rmdir(const char *pathname);

// dirent 结构体和 d_type
struct dirent
{
// 此目录进入点的inode
ino_t d_ino;
// 目录文件开头至此目录进入点的位移
off_t d_off;
// d_name 的长度, 不包含NULL字符
unsigned short int d_reclen;
// d_name 所指的文件类型
unsigned char d_type;
// 文件名
char d_name[256];
};

// 目录遍历函数
DIR *opendir(const char *name);
struct dirent *readdir(DIR *dirp);
int closedir(DIR *dirp);

// dup、dup2函数
int dup(int oldfd);// 复制文件描述符
int dup2(int oldfd, int newfd);// 重定向文件描述符

// fcntl函数 复制文件描述符,设置/获取文件的状态标志
int fcntl(int fd, int cmd, ... /* arg */ );

参考链接

https://www.nowcoder.com/study/live/504


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!