Linux系统编程入门(上)

网络编程系列文章

第1章 Linux系统编程入门(上)
第1章 Linux系统编程入门(下)

第2章 Linux多进程开发(上)
第2章 Linux多进程开发(下)

第3章 Linux多线程开发

第4章 Linux网络编程


第5章 Web服务器

第一章 Linux系统编程入门(上)

1.1 GCC

  • GCC 原名为 GNU C 语言编译器( GNU C Compiler)
  • GCC ( GNU Compiler CollectionGNU 编译器套件)是由 GNU 开发的编程语言译器。 GNU 编译器套件包括 CC++Objective-CJavaAdaGo 语言前端,也包括了这些语言的库(如 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++ 的区别

  • gccg++ 都是 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 常用参数选项

在这里插入图片描述

扫描二维码关注公众号,回复: 17524736 查看本文章
# 1. 将test.c文件预处理为test.i文件
gcc test.c -E -o test.i

# 2. 编译,生成test.s文件
gcc test.i -S -o test.s

# 3. 汇编,生成test.o文件
gcc test.s -s -o test.o

# 4. 执行
./test.o

# 也可以直接运行下面代码,生成 test.o文件
gcc test.c

在这里插入图片描述

1.2 静态库

(1)什么是库

  • 库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,它提供给使用
    者一些可以直接拿来用的变量函数

  • 是特殊的一种程序,编写库的程序和编写一般的程序区别不大,只是库不能单独运行。

  • 文件有两种,静态库动态库共享库),区别是:

    • 静态库在程序的 链接阶段被复制到了程序中;
    • 动态库链接阶段 没有 被复制到程序中,而是程序在 运行 时由系统动态加载到内存中供程序调用。
  • 库的好处:

    • 代码保密
    • 方便部署分发

(2)静态库的制作

  • 命名规则
    • 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:索引
# 1. 编译汇编(不链接)获得 `.o` 文件
gcc -c add.c div.c mult.c sub.c

# 2. 将 .o 文件打包
ar rcs libcalc.a add.o sub.o mult.o div.o

在这里插入图片描述

(3)静态库的使用

gcc main.c -o app -I ./include/ -l calc -L ./lib/

在这里插入图片描述

1.3 动态库

(1)动态库的制作

  • 命名规则

    • Linux : libxxx.so
      lib : 前缀(固定)
      xxx : 库的名字,自己起
      .so : 后缀(固定)
    • Windows : libxxx.dll
  • 动态库的制作

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

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

      -fpic 用于编译阶段,产生的代码没有绝对地址,全部用 相对地址,这正好满足了共享库的要求,共享库被加载时地址不是固定的。如果不加 -fpic ,那么生成的代码就会与位置有关,当进程使用该 .so 文件时都需要重定位,且会产生成该文件的副本,每个副本都不同,不同点取决于该文件代码段与数据段所映射内存的位置。

    • gcc 得到动态库

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

      在这里插入图片描述

(2)动态库的使用

gcc main.c -o main -I ./include/ -l calc -L ./lib/ 

# 运行,会报错,找不到共享库
./main
# error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory

工作原理

  • 静态库GCC 进行链接时,会把静态库中代码 打包到 可执行程序中

  • 动态库GCC 进行链接时,动态库的代码 不会被打包 到可执行程序中

  • 程序启动 之后,动态库 会被 动态加载 到内存中,通过 ldd (list dynamic dependencies )命令检查动态库依赖关系

    在这里插入图片描述

  • 如何定位共享库文件呢?
        当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道 绝对路径。此时就需要系统的 动态载入器 来获取该绝对路径。对于 elf 格式的可执行程序,是由 ld-linux.so 来完成的,它先后搜索 elf 文件的

    • DT_RPATH 段(无法修改) —> 环境变量 LD_LIBRARY_PATH—> /etc/ld.so.cache 文件列表 —> /lib//usr/lib 目录找到库文件后将其载入内存

(3)解决动态库加载失败问题

修改上面的搜索路径

a. 修改环境变量 LD_LIBRARY_PATH

  • 临时配置

    • 在终端直接配置。(关闭当前会话框,将失效)

      # 原来的拼接上动态库的绝对路径
      export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/rmzh/projects/Linux/lesson04/library/lib
      

      在这里插入图片描述

  • 永久配置

    • 用户级配置

      # 修改 ~/.bashrc 文件
      vim ~/.bashrc
      
      # 在最后面加入:
      export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/rmzh/projects/Linux/lesson04/library/lib
      
      # 保存退出后,使生效
      source ~/.bashrc
      

      在这里插入图片描述

    • 系统级别

      # 修改 /etc/profile 文件
      sudo vim /etc/profile
      
      # 在最后面加入:
      export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/rmzh/projects/Linux/lesson04/library/lib
      
      # 保存退出后,使生效
      source /etc/profile
      

b. 修改 /etc/ld.so.cache 文件

  • 该文件是二进制文件,不能直接修改,可以间接修改

    # 修改 /etc/ld.so.conf 文件
    sudo vim /etc/ld.so.conf
    
    # 加入:
    /home/rmzh/projects/Linux/lesson04/library/lib
    
    # 保存退出后,更新生效
    sudo ldconfig
    

c. 将动态库文件放到 / lib//usr/lib/ 目录下

  • 不推荐使用!
  • 因为这两个目录下本身就包含系统自带的库文件,可能名称会用相同,会覆盖系统文件

1.4 静态库与动态库的对比

(1)程序编译成可执行程序的过程

在这里插入图片描述

  • 预处理:头文件展开、删除注释、宏的替换
  • 编译:编译成汇编代码
  • 汇编:汇编成目标代码
  • 链接:处理静态库和动态库,链接成可执行文件

(2)静态库制作过程

在这里插入图片描述

(3)动态库制作过程

在这里插入图片描述

(4)静态库的优缺点

优点 缺点
静态库被打包到应用程序中加载速度快 消耗系统资源,浪费内存
发布程序无需提供静态库,移植方便 更新、部署、发布麻烦

在这里插入图片描述

(5)动态库的优缺点

优点 缺点
可以实现进程间资源共享(共享库) 加载速度比静态库慢
更新、部署、发布简单 发布程序时需要提供依赖的动态库
可以控制何时加载动态库

在这里插入图片描述

库比较小 的话使用 静态库比较大 就使用 动态库

1.5 Makefile

(1)什么是 Makefile

  • 一个工程中的源文件不计其数,其按类型功能模块分别放在若干个目录中,Makefile 文件 定义了 一系列的规则 来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile 文件就像一个 Shell 脚本 一样,也可以执行操作系统的命令。
  • Makefile 带来的好处就是 “自动化编译”,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。 make 是一个命令工具,是一个解释 Makefile 文件中指令的命令工具,一般来说,大多数的 IDE 都有这个命令,比如 DelphimakeVisual C++nmakeLinuxGNUmake

(2)Makefile 文件命名和规则

  • 文件命名
    makefile 或者 Makefile

  • Makefile 规则

    • 一个 Makefile 文件中可以有一个或者多个规则
      目标 … : 依赖 …
      命令(Shell 命令)
      • 目标:最终要生成的文件(伪目标除外)
      • 依赖:生成目标所需要的文件或是目标
      • 命令:通过执行命令对依赖操作生成目标(命令前必须 Tab 缩进
    • Makefile 中的其它规则一般都是为 第一条规则 服务的。
  • 例如:

    # 创建 Makefile 文件
    vim Makefile
    
    # 添加以下内容(第一种)
    app:add.c sub.c mult.c div.c main.c
    	gcc add.c sub.c mult.c div.c main.c -o app
    
    # 保存退出,输入make
    make
    

    在这里插入图片描述

(3)工作原理

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

    • 如果存在,执行命令

    • 如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的,
      如果找到了,则执行该规则中的命令

      # 第二种
      # 该效率优于第一种,如果其中一个依赖改变,只需编译这一个即可
      
      app:add.o sub.o mult.o div.o main.o
      	gcc add.o sub.o mult.o div.o main.o -o app
      	
      add.o:add.c
      	gcc -c add.c -o add.o
      
      sub.o:sub.c
      	gcc -c sub.c -o sub.o
      	
      mult.o:mult.c
      	gcc -c mult.c -o mult.o
      	
      div.o:div.c
      	gcc -c div.c -o div.o
      	
      main.o:main.c
      	gcc -c main.c -o main.o
      
  • 检测更新,在执行规则中的命令时,会比较目标依赖文件的时间修改更新的时间

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

(4)变量

  • 自定义变量
    • 变量名 = 变量值 : var=hello
  • 预定义变量
    • AR : 归档维护程序的名称,默认值为 ar
    • CC : C 编译器的名称,默认值为 cc
    • CXX : C++编译器的名称,默认值为 g++
    • $@ : 目标的完整名称
    • $< : 第一个依赖文件的名称
    • $^ : 所有的依赖文件
  • 获取变量的值
    $(变量名) : $(var)
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 $@
	
# 定义变量
src=add.o sub.o mult.o div.o main.o
target=app
$(target):$(src)
	$(CC) $(src) -o $(target)

(5)模式匹配

add.o:add.c
	gcc -c add.c

sub.o:sub.c
	gcc -c sub.c
	
mult.o:mult.c
	gcc -c mult.c
	
div.o:div.c
	gcc -c div.c
	
main.o:main.c
	gcc -c main.c
  • %.o:%.c
    • % : 通配符,匹配一个字符串
    • 两个 %匹配的是同一个字符串
# 上面的5种都能匹配
%.o:%.c
        gcc -c $< -o $@

(6)函数

  • $(wildcard PATTERN...)

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

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

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

    • 示例
      $(wildcard *.c ./sub/*.c)
      返回值格式: a.c b.c c.c d.c e.c f.c

      # add.c sub.c mult.c div.c main.c
      src=$(wildcard ./*.c)
      
  • $(patsubst <pattern>, <replacement>, <text>)

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

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

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

    • 示例
      $(patsubst %.c, %.o, x.c bar.c)
      返回值格式: x.o bar.o

      # 第二种
      # add.c sub.c mult.c div.c main.c
      src=$(wildcard ./*.c)
      objs=$(patsubst %.c, %.o, $(src))
      target=app
      $(target):$(objs)
              $(CC) $(objs) -o $(target)
      
      %.o:%.c
              gcc -c $< -o $@
              
      # 删除.o文件
      # 伪目标
      .PHONY:clean
      clean:
      		rm $(objs) -f
      # make调用在终端输入:make clean
      

      在这里插入图片描述

注:仅供学习参考,如有不足,欢迎指正!

猜你喜欢

转载自blog.csdn.net/weixin_43412762/article/details/136380543