Linux系统编程1

快捷键

ctrl+a:光标移到命令行最前面

ctrl+e:光标移到命令行最后面

ctrl+u:删除整行

gcc编译的四个阶段

  • 预处理:gcc -E helloworld.c -o helloworld.i

  • 编译:gcc -S helloworld.i -o helloworld.s 编译阶段出错,会给出行号

  • 汇编:gcc -c helloworld.s -o helloworld.o

  • 链接:gcc helloworld.o -o helloworld 链接阶段出错,会有collect: error: ld

gcc编译常用命令

  • -o:指定生成的文件
  • -I :指定头文件的路径
  • -g:编译时添加调试语句(要使用gdb调试时必须加这个参数)
  • -Wall:显示所有警告信息
  • -D:向当前程序中注册一个宏,如gcc hello.c -D HELLO就是注册了一个HELLO的宏用作开关来输出调试信息

静态库和动态库

静态库会在链接的时候直接把库文件复制到程序中,运行的时候不再依赖库文件。以.a为后缀

动态库是在运行时动态加载。以.so为后缀

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
#include<math.h>
int main(int argc,char *argv[])
{
printf("hello 编程珠玑\n");
int b = 2;
double a = exp(b);
printf("%lf\n",a);
return 0;
}

对于这个函数,使用了math.h中的exp函数

静态链接

1
gcc -static main.c -o main -lm

动态链接

1
gcc main.c -o main -lm

使用了-l参数,后边的mmath.h的基本名称

可以使用ldd main查看动态链接文件的都链接了哪些库

1
2
3
4
5
wufang@wufang:~/C++_base_learning$ ldd test1
linux-vdso.so.1 (0x00007ffca1fc5000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f2cb480f000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2cb4400000)
/lib64/ld-linux-x86-64.so.2 (0x00007f2cb490b000)

如何判断一个函数是否需要链接呢?

1
man 3 函数名

静态库制作

  1. 将.c文件编译生成.o文件 gcc -c add.c -o add
  2. 使用ar工具制作静态库 ar rcs lib库名.a add.o sub.o
  3. 编译静态库到可执行文件中gcc main.c lib库名.a -o main

按这样三部编译出可执行文件后,会出现如下警告

1
2
3
4
5
6
7
test.c:8:33: warning: implicit declaration of function ‘add’ [-Wimplicit-function-declaration]
8 | printf("%d+%d=%d\n",a,b,add(a,b));
| ^~~
test.c:9:33: warning: implicit declaration of function ‘sub’ [-Wimplicit-function-declaration]
9 | printf("%d-%d=%d\n",a,b,sub(a,b));
| ^~~
wufang@wufang:~/C++_base_learning$ ./test

这个警告告诉:对add的隐式声明,因此,我们需要额外对使用的库函数进行声明,写在一个库名.h文件中,文件格式如下

1
2
3
4
5
6
7
#ifndef _MYMATH_H_
#define _MMYMATH_H_

int add(int,int);
int sub(int,int);

#endif

这使用了头文件首位,目的是:防止重复引入和重复定义。https://www.cnblogs.com/flowingwind/p/8304668.html

制作好.h文件后,在.cpp文件中引入.h头文件(#include "mymath.h")。此时的编译命令为

1
gcc main.c ./lib/lib库名.a -o main.out -I ./inc

静态库文件放在lib中,头文件放在inc中

动态库制作

参考:https://cloud.tencent.com/developer/article/1711778

  1. 将.c文件编译生成与位置无关的二进制文件:gcc -c add.c -o add.o -fPIC

  2. 使用gcc -shared制作动态库:gcc -shared -o lib库名.so add.o sub.o div.o

  3. 编译可执行程序,指定所使用的动态库。 -l指定库名,-L指定动态库路径, -I指定库的头文件路径

    gcc test.c -o test.out -l库名 -L./lib -I ./inc

注意:-l后边紧跟库名,-L后紧跟库路径,空格可有可无

按上边步骤完成后,运行可执行文件,报错

1
2
3
wufang@wufang:~/C++_base_learning/dynamic$ gcc test.c -o test.out -lmymath -L./lib -I./inc
wufang@wufang:~/C++_base_learning/dynamic$ ./test.out
./test.out: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory

这个报错是因为动态链接器在运行程序的时候找不到需要链接的库。解决办法有三种,如下给出视频中的方法:

即 将动态库的路径写入用户终端配置文件.bashrc中,

1
2
3
vim ~/.bachrc  #打开用户自己的终端配置文件
export LD_LIBRARY_PATH=.... #添加链接路径,最好使用绝对路径
source .bashrc #运行生效

解决方法合集:https://www.jianshu.com/p/a3b2b6f5f0fc

gdb调试工具

基础指令:

  • -g:使用该参数编译可执行文件,得到调试表
  • gdb ./a.out
  • set listsize 行数:设置list打印行数
  • list 1:从第一行开始打印
  • b/break:设置断点,后边可以跟着行号或者函数名
  • run:直接运行程序,如果设置断点了,会运行到断点,如果程序出错了,会直接运行到出错位置
  • s/step:下一条指令(会进入函数)
  • n/next:下一条指令(越过函数)
  • finish:进入函数后,结束当前函数调用,即退出该函数
  • p/print:p i 查看变量i的值
  • c/continue:运行程序到下一个断点或者结束
  • quit:退出gdb

其它指令:

  • start:从main函数的第一行开始

  • set args:设置main函数命令行参数

  • info b:查看所有断点信息

  • b 20 if i = 5设置条件断点,用在循环语句,嵌套递归中

  • bt:查看程序中正在存活的栈帧

  • fram 栈帧编号:切换栈帧

  • display 变量:跟踪变量

  • undisplay 变量编号:取消跟踪

Makefile

简易版

test.c中是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 这里将函数声明写在了main函数中,后续会给出将函数声明写在.h中
int add(int,int);
int sub(int,int);

int main(int argc, char *argv[])
{
int a = 14, b = 7;
printf("%d+%d=%d\n",a,b,add(a,b));
printf("%d-%d=%d\n",a,b,sub(a,b));
return 0;
}

1个规则:

目标:依赖条件

​ (一个Tab缩进)命令

1
2
3
4
5
6
7
8
9
10
11
test:test.o add.o sub.o
gcc test.o add.o sub.o -o test

test.o:test.c
gcc -c test.c -o test.o

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

sub.o:sub.c
gcc -c sub.c -o sub.o

基本原则:

  1. 若想生成目标文件,检查规则中的依赖条件是否存在,如不存在,则寻找是否有规则用来生成该依赖文件
  2. 检查规则中的目标是否需要更新,必须先检查它的所有依赖,依赖中有任何一项被更新,则目标更新

2个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
src = $(wildcard *.c)   #找到当前目录下所有后缀未.c的文件,赋值给src
obj = $(patsubst %.c, %.o $(src)) #将src变量中包含.c后缀的文件都替换成.o后缀

test:@(obj)
gcc @(obj) -o test

test.o:test.c
gcc -c test.c -o test.o

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

sub.o:sub.c
gcc -c sub.c -o sub.o

clean:
-rm -rf $(obj) test #将生成的.o文件和可执行文件都删了,rm前的"-"表示出错依然执行

3个变量:

$@:在一条规则的命令中,表示该规则的目标

$^:在一条规则的命令中使用,用于表示这条规则的所有依赖条件

$<:在一条规则的命令中使用,用于表示这条规则的依赖条件列表中的第一个依赖条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
src = $(wildcard *.c)  
obj = $(patsubst %.c, %.o, $(src))

test:$(obj)
gcc $^ -o test

test.o:test.c
gcc -c $< -o test.o

add.o:add.c
gcc -c $< -o add.o

sub.o:sub.c
gcc -c $< -o sub.o
clean:
-rm -rf $(obj) test

模式规则:

1
2
3
4
5
6
7
8
9
test.o:test.c
gcc -c $< -o test.o
add.o:add.c
gcc -c $< -o add.o
sub.o:sub.c
gcc -c $< -o sub.o
# 可以将上面的结构重复的代码做如下替换
%.o:%.c
gcc -c $< -o $@

最终的makefile代码可以这样写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
src = $(wildcard *.c)  
obj = $(patsubst %.c, %.o, $(src))

CXX = gcc #编译器,可以根据需要改成g++
myArgs = -Wall -g #调试选项,使得编译的文件可以进行调试

test:$(obj)
$(CXX) $^ -o $@ $(myArgs)
%.o:%.c
$(CXX) -c $< -o $@ $(myArgs)

.PHONY:clean #伪目标,因为文件夹中可能存在同名的clean文件
clean:
-rm -rf $(obj) test

重构版

项目文件夹下有如下

1
inc       makefile  obj       src

其中,inc文件夹存放头文件src文件夹存放.c或者.cpp源代码obj文件夹存放.o目标文件

makefile文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
src = $(wildcard ./src/*.c)
obj = $(patsubst ./src/%.c, ./obj/%.o, $(src))

inc_path = ./inc #头文件所在位置
myArgs = -Wall -g


test:$(obj)
gcc $^ -o test $(myArgs)

$(obj):./obj%.o:./src%.c
gcc -c $< -o $@ $(myArgs) -I $(inc_path) #在编译生成目标文件时需要指出头文件位置
clean:
-rm -rf $(obj) test