第三阶段学习:Linux C高级

一、Linux基础

1、什么是Linux

内核:能够让计算机硬件进行工作,能够运行的最核心的功能组合。Linux内核:Linus和Linux社区的人员一起研发出来的内核,Linux内核是免费开源的内核程序。在Linux内核基础上,加上各个厂商匹配内核开发出网络管理、桌面系统、快捷功能,形成完整的操作系统。操作系统:ubuntu、debian、redhat、centos stream、arch linux。

  • Linux系统体系结构

    Linux内核、shell、文件系统、应用程序

    image-20230304084439369

2、Linux内核

​ 内核是Linux系统体系中的最底层,提供系统中核心功能,并允许有序访问硬件资源,管理:输入输出设备、进程执行情况、文件系统操作、内存资源管理。Linux内核支持:多任务、多用户

3、shell

​ shell命令解释器,壳,用于保护Linux内核。当程序执行时,把程序的各种操作指令进行解析执行,通过shell命令解释器就让内核执行对应操作。作用:提供一个方式可以让用户和内核(操作系统)进行交互。

4、文件系统

​ 文件系统用于管理和组织计算机存储设备上的大量文件,在Windows系统使用NTFS格式文件系统,在Linux系统中目前一般使用ext4格式文件系统。Linux中文件系统是把文件组织为一棵倒置的树,每个文件夹相当于树枝,文件相当于树叶,Linux中一切皆文件

(1)文件夹(目录)
文件系统只有一个起始位置:根目录 / ,是整个磁盘起始位置,文件系统就是一个树型的目录结构。

(2)目录结构(存储结构)

位置描述
/根目录
/boot存放Linux和系统的启动文件
/bin存放系统中最常用的可执行程序(命令)
/dev存放系统中的设备文件(设备在Linux系统中被表示为文件),包括鼠标、键盘、磁盘等各种设备
/etc存放系统的配置文件,如:hostname存放主机名
/home普通用户的家目录的默认位置
/lib存放共享库,/lib32:存放32位的共享库,/lib64:存放64位的共享库
/media存放cd、软盘、usb临时读入的文件
/mnt挂载文件系统挂载点
/opt作为可选程序和文件存放目录
/proc作为进程(当前执行程序)的文件存放目录
/root系统管理员用户的目录(root用户)
/sbin作为扩展的、更多的二进制程序存放的目录
/sys系统运行时的文件存放目录
/tmp临时文件存放目录
/usr标准库、第三方库存放目录

(3)文件分类
Linux中一切皆文件,存在多种文件类型:普通文件、目录文件、设置文件等等,把文件分为了7类

文件类型符号说明
普通文件字符文件、二进制。Linux中最多的一种文件类型, 包括纯文本文件(ASCII);二进制文件(binary);数据格式的文件(data);各种压缩文件.tar、.tar.gz、.tar.bz、.tgz、.zip
目录文件d文件夹。就是目录, 能用 cd 命令进入
块设备文件b块设备文件:存储。就是存储数据以供系统存取的接口设备,简单而言就是硬盘。例如一号硬盘的代码是 /dev/hda1等文件
字符设备文件c字符设备文件:鼠标、键盘。即串行端口的接口设备,例如键盘、鼠标等等
套接字文件s网络编程通信文件。这类文件通常用在网络数据连接,可以启动一个程序来监听客户端的要求,客户端就可以通过套接字来进行数据通信
管道文件p进程通信文件。FIFO也是一种特殊的文件类型,它主要的目的是解决多个程序同时存取一个文件所造成的错误。FIFO是first-in-first-out(先进先出)的缩写
符号链接文件l快捷方式。类似Windows下面的快捷方式

(4)文件属性
通过“ ls -l ”命令查看文件、目录的详细信息(文件属性)

-rw-rw-r-- 1 ubuntu ubuntu   656 224 17:11 study.c	//普通文件
drwxrwxr-x 2 ubuntu ubuntu  4096 33 16:28 one		//目录

说明

文件类型文件权限文件硬链接数文件所有者文件所属组文件大小文件最后修改时间文件名
第1个字符第2-10共9个字符数字用户名用户名组文件的内容所占空间大小(数字)如果修改了文件,最后一次修改的时间文件名字
-rw-rw-r–1ubuntuubuntu6562月 24 17:11study.c
drwxrwxr-x2ubuntuubuntu40963月 3 16:28one

(5)文件权限

  • Linux文件权限分为三组:文件所有者权限、文件所属组权限、其他用户权限
  • Linux文件权限类型分为3种:读、写、执行
第一组第二组第三组
用户权限用户组权限其他用户权限
读、写、执行读、写、执行读、写、执行
3位3位3位
  • 第1位读的权限,为1表示有权限,为0表示没有权限
  • 第2位写的权限,为1表示有权限,为0表示没有权限
  • 第3位执行权限,为1表示有权限,为0表示没有权限

在显示权限时

  • 如果第1位为 1,有读权限显示 r,如果第1位为 0,没有读权限显示 –
  • 如果第2位为 1,有写权限显示 w,如果第2位为 0,没有写权限显示 –
  • 如果第3位为 1,有执行权限显示 x,如果第3位为 0,没有执行权限显示 –

例如:

r--rw----	//r-- rw- --- : 用户可读,组可读写,其他用户没有任何权限
//权限二进制表示:
100 110 000
//权限八进制表示:
0460	//前面加数字0表示八进制

如果文件是目录

  • 文件的硬链接数,表示目录中的目录文件个数
  • 文件权限
    • r 读:表示能够查看目录下有什么内容;
    • w 写:表示能够在目录下执行创建、删除文件;
    • x 执行:表示能够访问目录

5、shell命令

​ 使用shell命令操作Linux系统(字符界面),shell命令完成内核与用户之间的交互。shell命令就是shell解释器能够识别的命令,能够让内核执行对应操作。shell命令的输入:在终端提示符,标识命令输入的位置。

(1)路径表示

  • 相对路径
    默认从当前的工作路径位置开始,找到对应的文件或目录的位置,例如:…/…/tmp/VMwareDnD/ok.txt
  • 绝对路径
    对于Linux文件系统,从根目录 / 开始,来表示一个文件的位置,例如:/tmp/VMwareDnD/ok.txt
  • 特殊路径表示
    ~ :家目录,当前用户的用户目录
    . :当前目录
    .. :当前目录的上一级目录
    / : 根目录

(2)命令格式

command + [option] + [argument]
  命令       选项         参数
//命令:shell要解析的指令内容,要执行的内容
//选项:改变命令执行动作的类型(命令功能选择),用:-选项表示,选项可以有多个:-选项1 -选项2 -选项3 -选项4 或 -选项1选项2
//参数:命令要作用的目标
  • man [command]:查看手册中该命令的使用说明

    man rm	//查看rm命令的详细使用说明,按q退出!
    

(3)常用命令

  • pwd:在文件系统中,显示当前的工作路径(绝对路径),pwd没有选项和参数

  • cd :改变切换当前的工作路径

    cd 参数(新的工作路径)
    
  • mkdir:创建目录文件

    mkdir 参数(要创建的目录路径)
    
    • -p :参数,如果目录路径不存在,则依次创建,直到最终要创建的目录创建完成
  • touch:创建普通文件

    touch 文件路径	//如果文件已经存在,则会修改文件的最后修改时间,不会清空该文件!!!
    
  • cat :查看显示指定文件的内容

    cat 文件路径
    
    • -E :参数,在每一行结束时添加一个 $ 符号
  • file :查看文件类型

    file 文件路径
    
  • cp :将目录或文件拷贝到另一个路径中

    cp 源文件路径 目标文件路径
    
    • -r :参数,拷贝目录文件
  • mv :将源(目录)文件移动到另一个路径中

    mv 源文件(目录) 目标文件路径
    
  • rm :删除文件或目录

    rm 删除的文件路径
    
    • -r :参数,删除目录以及目录中的所有文件
  • diff :比较两个文件,显示出不同的地方

    diff 文件1 文件2
    
  • ls :列出对应文件、目录的信息,默认列出当前目录的信息

    ls 路径		//ls:不写路径,默认列出当前目录
    
    • -a :参数,列出目录中的所有文件(包括隐藏文件)
    • -l :参数,列出文件、目录的详细信息(文件属性)
  • ln :创建链接文件(默认创建硬链接文件

    • 符号链接文件
      也叫做软链接文件,就是对文件创建一个快捷方式,操作符号链接文件就是操作源文件,符号链接文件只是对源文件引述。

      • -s:参数,对源文件建立符号链接,而非硬链接
      ln -s 源文件 目标符号链接文件
      
    • 硬链接文件
      在文件系统中,创建一份和源文件完全一致的独立文件,新文件和源文件会一直保持一致,删除一个文件对其他文件不会有影响

      ln 源文件 目标硬链接文件	//ln不加参数,默认创建硬链接文件
      
  • tar :归档与压缩
    归档文件:把多个文件打包成一个文件。压缩文件:把已经打包的文件进行压缩处理

    • -A :参数,新增文件到已存在的备份文件

    • -d :参数,记录文件的差别

    • -t :参数,列出备份文件的内容

    • -u :参数,添加改变了和现有的文件到已经存在的压缩文件

    • -c :参数,建立新的备份文件

    • -x :参数,从备份文件中还原

    • -v :参数,显示操作过程;

    • -f :参数,指定备份文件;这个参数是最后一个参数,后面只能接档案名!

    • -z :参数,使用gzip算法进行压缩与解压

    • -j :参数,使用bzip2算法进行压缩与解压

    a、创建归档文件: 把多个文件合并成一个文件

    tar -cvf 归档文件名.tar 文件1 文件2 文件3 ......
    

    b、释放归档文件:把归档文件中合并打包的文件释放出来

    tar -xvf 归档文件名.tar
    

    c、使用gzip算法进行压缩与解压( -z )

    • 压缩

      tar -czvf 压缩文件名.tar.gz 文件1 文件2 文件3 ......
      
    • 解压

      tar -xzvf 归档文件名.tar.gz
      

    d、使用bzip2算法进行压缩与解压( -j )

    • 压缩

      tar -cjvf 压缩文件名.tar.bz2 文件1 文件2 文件3 ......
      
    • 解压

      tar -xjvf 归档文件名.tar.bz2
      

    e、使用zip算法压缩与解压

    • 压缩
    zip 压缩文件名.zip 文件1 文件2 文件3 ......
    
    • 解压

      unzip 压缩文件名.zip
      

6、用户管理

(1)修改用户密码
在工作用户下只能修改当前用户的密码,如果要修改其他用户的密码,必须是管理员权限(身份)。在命令前加上sudo,表示在当前命令执行时临时提升为管理员root权限

passwd 用户名	//只能修改当前用户的密码
sudo passwd 用户名	//提权后可以修改任意用户的密码

(2)切换用户

su 用户名	//切换用户
exit //退出当前用户

(3)添加用户
只有管理员root能添加

sudo adduser 用户名	//提权后以管理员root身份去创建新用户

注意:新添加的用户不具备提升为管理员运行的权限,不能使用sudo。此时需要切换到root用户或者已具备提权的用户去修改配置文件:/etc/sudoers,在文件最后添加:用户名 ALL=(ALL:ALL)ALL

(4)删除用户

sudo userdel -r 用户名

7、用户组管理

(1)创建用户组

sudo groupadd 用户组名

(2)把用户添加到用户组

sudo gpasswd -a 用户名 用户组

(3)把用户从用户组删除

sudo gpasswd-d 用户名 用户组

(4)删除用户组

sudo groupdel 用户组名

8、权限修改

​ 修改文件的操作权限(读、写、执行),执行修改权限,需要文件的所属用户才能执行修
改,root用户权限也是可以更改

(1)chmod :修改文件权限

sudo chmod 权限 文件
  • 权限
    • u + [r、w、x] :在用户权限中添加权限
    • u - [r、w、x] :在用户权限中删除权限
    • g + [r、w、x] :在用户组权限中添加权限
    • g - [r、w、x] :在用户组权限中删除权限
    • o + [r、w、x] :在其他用户权限中添加权限
    • o - [r、w、x] :在其他用户权限中删除权限

注意
权限是用二进制位来表示,1表示有,0表示无,可以通过chmod直接设置每一位的值。对于目录,执行权限 x 表示可以进入该目录!

//1、使用二进制方式来表示权限值,进行设置
chmod 110110110 xxx.txt //设置xxx.txt文件权限是:用户拥有读写、用户组拥有读写、其他用户拥有读写
//2、使用八进制方式来表示权限值,进行设置(八进制前要加0)
chmod 0666 xxx.txt //设置Xxx.txt文件权限是:用户拥有读写、用户组拥有读写、其他用户拥有读写
  • 在创建文件时
    • 普通文件权限:0664
    • 目录文件权限:0775

(2)chown :改变文件所属者
要改变所属者,必须是root管理员身份运行

chown 用户名 文件

(3)chgrp :改变文件所属组
要改变所属组,必须是root管理员身份运行

chgrp 用户组 文件

9、输出重定向

(1)> :输出到某个位置的内容变为输出到指定的位置中(会删除指定位置的原先内容)

(2)>> :以追加方式输出到指定位置

注意

  • echo :在命令行终端输出指定内容

    echo 字符串	
    

例如:

echo helloworld  //输出 helloworld
echo helloworld > 1.txt	  //输出helloworld到1.txt文件中,1.txt原内容会被清空!
echo helloworld >> 1.txt  //在1.txt的末尾添加helloworld,1.txt原内容不会被清空!

10、Linux通配符

​ 使用一些特殊的符号来代替文件名的字符

(1)* :匹配(代替)任意长度的字符串

file_*.txt //能匹配:file_abc.txt, file_ok.txt, file_12345.txt 不能匹配:fi1e_ok123

(2)? :匹配(代替)任意的一个字符

file_?.txt  //能匹配:file_1.txt, file_a.txt, file_q.txt 不能匹配:fi1e_12.txt

(3)[ … ] :匹配(代替)特定的(方括号中写出的)一个字符

file_[abc123].txt  //能匹配:file_a.txt, file_1.txt, file_c.txt 不能匹配:file_5.txt

(4)[ – ] :匹配(代替)特定的(方括号指定范围)一个字符

file_[a-e].txt  //能匹配:file_a.txt, file_b.txt, file_e.txt 不能匹配:file_ f.txt

(5)[ ^… ] :匹配(代替)特定的(除了方括号写出)一个字符

file_[^159a].txt  //能匹配:file_2.txt, file_6.txt, file_b.txt 不能匹配:file_5.txt

11、软件包管理

​ 软件两种使用方式:直接执行二进制程序,打开软件使用;安装包进行安装,然后进行使用。软件存在依赖(需要有其他环境的支持),软件包管理:软件以及对应的依赖说明。debian Linux软件包管理机制:deb;red hat Linux软件包管理机制:rpmubuntun软件包管理机制:deb

  • 软件包管理器

    • dpkg : 本地软件包管理器(离线安装,不能自动处理依赖

      • 安装本地软件包

        dpkg -i 软件包名.deb路径
        
      • 查看本机安装的软件包

        dpkg -l
        
      • 卸载已安装的软件

        dpkg -r 软件名
        
      • 卸载已安装的软件以及配置文件

        dpkg -p 软件名
        
    • apt :在线联网软件包管理器(在线安装,可以自动处理依赖

      • 服务器地址列表文件:/etc/apt/sources.list

      • apt update :获取软件源地址中有哪些软件(获取软件列表)

      • 在线下载安装软件

        apt install 软件包名
        //例如,安装网络管理工具 net-tools
        sudo apt install net-tools
        
      • 卸载安装的软件

        apt remove 软件名
        

12、TFTP服务器

​ 类似于网盘,客户端与服务器模式:一个用于上传和下载文件的服务器。

  • 安装TFTP

    • 安装一个TFTP服务器:提供一个文件夹用于上传和下载

      apt install tftpd-hpa
      
    • 安装一个TFTP客户端:从服务端提供的文件夹下载文件,往服务端上传一个到所提供的文
      件夹中

      apt install tftp
      
  • 配置TFTP服务器
    选择哪个文件夹作为上传、下载文件夹

    • 编辑配置

      sudo vim /etc/default/tftpd-hpa
      
      • 修改配置文件内容如下

        TFTP_USERNAME = "tftp"			//服务名
        TFTP_DIRECTORY = "/srv/tftp"	//选择目录,可以自己改,例如改为: "/home/ubuntu/tftp"
        TFTP_ADDRESS = "0.0.0.0:69"		//允许哪些客户端地址进行上传下载
        TFTP_OPTIONS = "-c -l -s"	//允许服务端创建文件,即客户端可以上传文件到服务器 -c:允许创建 -l:允许列表查看 -s:允许存储
        
      • 配置服务端的文件夹路径后,需要在对应路径有一个工作目录

        mkdir /home/ubuntu/tftp
        
      • 将该工作目录权限设置为0777

        chmod 0777 /home/ubuntu/tftp
        
      • 重启TFTP服务

        sudo /etc/init.d/tftpd-hpa restart
        //其他命令
        start(开启服务)stop(停止服务)status(查看服务器状态)
        
  • 客户端上传下载

    • 连接服务器

      tftp 服务端地址	//例如:tftp 192.168.3.3
      
    • 上传

      put 文件名	//只能上传当前目录的文件,文件名不能含路径!
      
    • 下载

      get 文件名
      
    • 退出

      quit
      

13、NFS服务器

  • 安装NFS服务器

    sudo apt install nfs-kernel-server
    
    • 编辑NFS服务器配置文件

      sudo vim /etc/exports
      
      • 添加服务端共享目录

        /sv/homes	//提供共享的目录,可以自己改,例如改为: /home/ubuntu/nfs
        hostname1(rw,sync,no_subtree_check)	//hostname:表示允许哪些地址来访问(写*表示任意地址); rW:可读写; sync:同步; no_subtree_check:不检查目录结构
        hostname2(ro,sync,no_subtree_check)	//地址可以有多个!
        
    • 修改目录权限

      chmod 0777 /home/ubuntu/nfs
      
    • 重启NFS服务

      sudo /etc/init.d/nfs-kernel-server restart
      //其他命令
      start(开启服务)stop(停止服务)status(查看服务器状态)
      
  • 安装NFS客户端

    sudo apt install nfs-common
    
    • 使用NFS客户端挂载NFS服务端提供的目录
      使用mount挂载的方式,将NFS服务器的共享目录挂载到本地的一个目录,挂载成功后可以直接使用。客户端创建一个挂载点(目录),通常放在:/mnt/nfs

      • 挂载

        mount 192.168.3.38:/home/ubuntu/nfs /mnt/nfs
        //服务端ip地址:192.168.3.38    
        //服务端共享目录路径:/home/ubuntu/nfs
        //挂载到客户端本地的位置:/mnt/nfs
        
      • 解除挂载

        umount /mnt/nfs	//将挂载到客户端本地的位置进行解除
        

14、shell脚本

​ shell脚本是一种解释性的编程语言,可以将多个shell命令有序存放在文件中,由shell解释器
读取命令执行。多用于做命令控制,批量处理等操作。

(1)shell脚本

  • 文件通常以 .sh 作为后缀名

  • shell脚本注释,是以 # 作为标识

  • 给shell脚本添加可执行权限,并指定shell解释器来执行文件

    chmod u+x xxxx.sh  //1、添加可执行权限
    bash xxxx.sh  //2、用bash解释器来执行shell脚本
    

(2)shell脚本注意事项

  • 每一行就是一个shell命令
  • 编写规范和shell命令规范一致
  • 严格使用标点符号,包括空格!

(3)shell脚本编程

  • a、shell变量

    • 变量不需要指定类型,弱变量类型,一切变量皆认为是字符串

    • 定义

      变量名=//不能有空格
      
    • 变量的使用

      • $变量名:本质是字符串的替换

        " " 	//双引号,如果字符串内容有特殊字符,就解析特殊字符
        ' '     //单引号,如果字符串内容有特殊字符,都认为是普通字符
        ` `	    //反引号,执行命令得到的结果
        
      • 位置变量
        在执行shell脚本时,给定的参数或选项将通过位置变量进行传递(不需要定义

        $0:表示执行的shell脚本名称
        $1:表示第一个参数
        $2:表示第二个参数
        ...
        $9
        //注意:如果传入的参数超过10个:${10}、${11}、...	
        
      • 预定义变量

        • $# :shell脚本执行时的参数个数
        • $@ :所有位置参数值(不包括$0)
        • $* :所有位置参数值(不包括$0)
        • $? :上一条命令执行时的结束状态值
  • b、shell输入输出

    • 输出

      echo "输出内容"'输出内容'
      
    • 输入

      read 变量名1 变量名2 ......
      
  • c、算数运算

    expr 算数表达式	
    

    例如:

    a1=10
    a2=15
    c=`expr $a1 + $a2 - 5`
    echo $c
    
  • d、shell条件表达式

    • 字符串判断

      s1 = s2		//测试两个字符串的内容是否完全一致
      s1 != s2	//测试两个字符串的内容是否不一致
      -z s1		//测试字符串的长度是否为0
      -n s1		//测试s1字符串的长度是否不为0
      
    • 整数判断

      a -eq b	//测试a与b是否相等
      a -ne b	//测试a与b是否不相等
      a -gt b	//测试a是否大于b
      a -ge b	//测试a是否大于等于b
      a -lt b	//测试a是否小于b
      a -le b	//测试a是否小于等于b
      
    • test命令
      用于做对应的条件表达式的测试,得到结果,成立为0,失败为1,test运算结果不能用变量来存储,可以用 $? 来获取test的结果!

      test 表达式
      
      • 文件测试

        -d name	//测试name是否为目录
        -e name	//测试name文件是否存在
        -f name	//测试name文件是否为普通文件
        -L name	//测试name文件是否为符号链接
        -r name	//测试name文件是否存在且可读
        -w name	//测试name文件是否存在且可写
        -x name	//测试name文件是否存在且可执行
        

        例如:

        //例1
        if [ -d "/home/ubuntu/Desktop/test" ]
        then
            echo "当前是目录"
        fi
        //例2 $?存放上一次运算 test -d "/home/ubuntu/Desktop/test" 的结果,注意test运算结果不能用变量来存储!!!
        test -d "/home/ubuntu/Desktop/test" 
        if [ $? -eq 0 ]
        then
            echo "当前是目录"
        fi
        
  • e、shell中的 if 结构

    • 单分支

      if [ 条件表达式 ]
      then
          命令块
      fi
      
    • 双分支

      if [ 条件表达式 ]
      then
          成立命令块
      else
          不成立命令块
      fi
      
    • 多分支

      • if … elif … else 结构

        if [ 条件表达式1 ]
        then
            成立1命令块
        elif [ 条件表达式2 ]
        then
        	成立2命令块
        elif [ 条件表达式3 ]
        then
        	成立3命令块
        else
        	不满足命令
        fi
        
      • case 结构

        case 变量 in
            字符串1)
        	命令1
            ;;
        	字符串2)
        	命令2
        	;;
        	字符串3)
        	命令3
            ;;
        	*)	//*:匹配(代替)任意长度的字符串,此处类似于default,其余条件放*中
            命令n
            ;;
        esac
        
  • f、shell循环

    • while循环

      while [ 条件表达式 ]
      do
          满足条件执行
      done
      
    • for循环
      变量名能够从参数表中取值一次,就循环一次

      for 变量名 in 参数表
      do
          命令
      done
      
  • g、函数
    同C语言函数类似,是为了完成某个功能的命令块。对于函数如果有参数是不用添加的,是通过位置参数来标识,只需要在调用的时候写上值(在函数定义时不用写)

    • 函数定义

      函数名()
      {
      	命令
      }
      
    • 函数调用

      函数名 值12 ......
      
    • 函数返回值

      ​ 在函数中使用echo输出内容。在调用函数时,如果没有变量来进行存储赋值,就是打印;如果有变量进行赋值,则不会打印,此时会把echo要打印的内容存储到变量中。

二、Linux C高级

1、调试

(1)printf打印
使用printf打印信息,来确定是否执行,或值是否正确。

(2)gdb调试
在执行时,可以查看程序执行过程中的任意信息,且能够按照我们需要的方式进行执行:可以一步一步的执行,跟踪程序的执行过程。例如:可以让程序在没有执行完的情况下,停留在某条
语句位置,查看变量的值,查看程序到底执行了哪些代码,可以监控到程序的每个执行细节。Windows:windbg;Macos:lldb;Linux:gdbgdb可以调试:C、C++、go、opencl、object-c。如果需要调试程序,在编译时,需要支持调试。

gcc -g 文件名	//-g:编译程序时加上调试信息,例如:gcc -g test.c
  • a、打开调试功能
    命令行输入gdb打开调试功能,调试可运行的可执行程序。gdb

    (gdb) file 可执行程序文件名	//装入想要调试的可执行文件,例如 (gdb) file a.out
    
  • b、退出调试

    (gdb) q
    
  • c、gdb命令

    命令作用
    rrun 执行被调试的程序,会执行到第一个断点处(如果没有断点则执行到程序结束)
    b**break **设置断点在源代码中指定某一行设置断点:b xxxx
    ccontinue 当程序在某一个断点暂停,使用这个命令就继续执行,直到下一个断点或程序结束
    nnext 程序执行一行代码,执行当前行的代码
    sstep 执行一步代码,通常是执行一行,但是如果这一行是函数,就表示进入函数执行
    pprint 打印变量的值, p xxx:表示打印xxx这个变量的值
    ddelete 删除断点:delete xxx(断点号)
    info查看信息,比如查看断点、局部变量 :info b 、info locals
    start执行到main函数的第一句
    listlist n1 n2 :列出指定区域(n1到n2之间)的代码

    注意
    gdb tui:直接将屏幕分成两个部分,上面显示源代码,下面显示调试信息

    //方法一
    gdbtui -q 需要调试的程序名 //或用gdb -tui 需要调试的程序名
    //方法二
    直接使用gdb调试代码,先运行(start),然后使用切换键 Ctrl + X + A 调出 gdb tui
    

2、指针与数组

​ 程序的执行都在CPU中执行,在磁盘上的程序 需要把程序的指令拿到cpu中执行,效率不高,在计算机 中存在一个存储设备,用于存储当前正在执行的程序:内存

image-20230307185249340-1678186372354-1

​ 在程序执行时,所有内容都存储在内存中,每一个字节存储单元都分配了一个编号,把这个内存的编号叫做地址(内存地址)

image-20230307185516047-1678186520089-3

说明:在程序中,定义一个变量(int a=10),将数据10存储到变量a中,a变量就是所占用4个字节空间的数据,占用对应的内存空间4个字节(每个字节都有地址),那么数据存储到某个地方,就涉及到地址。依靠变量的地址,可以访问变量中的数据。存储数据,需要存储的内存地址。定义一个变量要占用内存空间,内存空间由地址来区别,所以变量名也只是表示了不同的空间。程序中通过一个变量名来定义变量,在内存中使用内存空间来存储数据,变量就是表示这个空间的数据,代表存储在内存空间中某个地址的数据内容。

image-20230307185809491-1678186691728-5

(1)指针
内存地址也是一个数据,可以用一个变量来存储内存地址。这种存储地址的变量就叫做指针变量(指针)指针就是存储一个数据内容的内存地址的一种数据类型(指针类型),即指针的内容就是另一个变量的地址。注意:指针存储地址,地址没有类型,只有地址中存储的数据有类型

image-20230307190029383-1678186832436-7
  • 指针的两层含义
    • 指针首先是一个变量,就拥有变量的所有属性:值和类型。它的类型是指针,值就是另一个变量的地址。指针变量也需要内存空间,存放其它变量的地址。
    • 指针的指向:存储哪个地址,就表示指向地址对应的空间

(2)指针变量定义
在定义指针时,存储变量的地址,指针存储地址后,怎么知道存储的地址对应的空间到底是什么类型数据如:p=0x10地址,怎么知道0x10地址对应的空间中是什么数据。所以定义指针变量时,需要确定指针存储的地址对应空间的数据类型

  • 定义指针变量

    指向数据类型 * 指针变量名
    
    • 指向数据类型:是指这个指针变量指向的数据类型,即指针变量存储地址,指针到底存储的是哪种数据类型数据的地址
    • * :在定义指针变量时,表示这是一个指针变量
    • 指针变量名 :指针变量的名字
  • 变量地址

    • & :取地址运算符
    • &变量名 :得到变量的地址
  • 间接取值符

    • * :取地址对应空间的值
    • * 指针变量 :取指针变量存储的地址,对应空间的值,会按照定义时指针变量指向的类型来获取!
int a = 10;
float * p;
p = &a;
printf("%f\n",*p);	//按照float类型去取值

(3)指针的运算
指针变量是可以存储数据的,但是存储的数据是地址,对于指针的运算只能有部分运算,对于指针的运算也是对地址的运算。

  • px表示指针变量

    • 加法( + )
      px + n :指针px向着地址增大的方向,移动n个指向数据类型大小的地址

    • 减法( - )
      px - n :指针px向着地址减小的方向,移动n个指向数据类型大小的地址

      • 特殊的减法
        px1 - px2指针减指针,要除以指针指向的类型,指两个地址间隔多少个数据

        int *px1,*px2;
        px1 = 0x1154;
        px2 = 0x114C;
        px1 - px2 = (0x1154-0x114C)/sizeof(int)= 8/4 = 2
        
    • 自增( ++ )

      • px++先使用px指针变量,然后 px = px + 1;先用px指针变量值(地址),然后px指针向地址增大方向移动一个数据类型大小存储到px指针变量中
      • ++px先 px = px + 1,然后使用px指针变量;先px指针向地址增大方向移动一个数据类型大小存储到指针变量中,然后使用px指针变量值(地址)
    • 自减( - - )

      • px- -先使用px指针变量,然后 px = px - 1;先用px指针变量值(地址),然后px指针向地址减小方向移动一个数据类型大小存储到px指针变量中

      • - -px先 px = px - 1,然后使用px指针变量;先px指针向地址减小方向移动一个数据类型大小存储到指针变量中,然后使用px指针变量值(地址)

        image-20230307190956580-1678187399164-9

(4)指针变量的大小
对于指针类型,由于指针在定义时说明的是存储哪种变量的地址,存储的数据类型和数据的地址不是同一个东西。不管是任意类型的指针变量,指针变量的大小都是一样的,是受计算机的内存限制:32位系统,占4字节;64位系统,占8字节。在同一系统中:sizeof(int *) == sizeof(double *) == sizeof(char *) == 4 或 8 。

int a;
double b;
int * p;
double * q;
p = &a;
q = &b;
printf("%ld\n",sizeof(p)); //求p这个变量对应的类型的大小,64位系统,为8字节
printf("%ld\n",sizeof(q)); //求q这个变量对应的类型的大小,64位系统,为8字节
printf("%ld\n",sizeof(int *)); //求int *这个指针变量对应的类型的大小,64位系统,为8字节
printf("%ld\n",sizeof(double *)); //求double *这个指针变量对应的类型的大小,64位系统,为8字节

(5)普通指针与一维数组

  • 一维数组:在内存中数组的元素是一段连续的空间,在这段空间中每个数据元素占用对应大小,元素与元素相邻。

    image-20230307191307597-1678187590723-11

​ 访问数组时,由于数组是连续的,如果指针变量存储了数组元素的地址,指针+1/-1得到当前数组元素后一个/前一个的地址(a[0]和a[1]数据元素地址是连续的,相隔就是一个数据类型大小)。

a、指针变量可以访问数组
只要指针存储数组第零个元素的地址,就可以访问整个数组

int * p = &a[0]; 
p+1 == &a[1]; 
p+2 == &a[2];
*(p+n) == a[n];

b、数组可以进行指针访问(数组当作指针)
对于数组而言,数组名有一定特殊含义,表示数组的首地址(数组中第零个元素的地址)

  • &a[0] :数组首地址 ,**&a[0] == a **;
  • a+n :移动n个数组元素大小的地址,可以移动到数组的每个元素的地址
  • *(a+n) :取对应地址中的数据,*(a+n) == a[n]

例如:

int a[5];
*(a+2) == a[2];

c、指针变量可以当做数组访问(指针当作数组)

  • 指针变量存储地址,可以存储数组首地址

    int a[5];
    int * p = a;	//或 int p = &a[0];
    
  • 数组名是数组首地址,a[i]访问

    a == p;
    a[i] == p[i];
    

d、指针和数组在本质上还是存在区别
数组名还可以表示整个数组(有整个数组空间存储数据);指针表示存储地址(指针大小为4/8,只是存储地址而已)

  • 数组名是一个常量,表示数组首地址,不可赋值!!!
  • 指针是一个变量,可以存储数组首地址,也可以存储其他地址
image-20230307193644058-1678189006088-15

(7)普通指针与字符数组
对于字符数组,如果是按照字符串进行操作(整体操作,‘\0’ 结束),字符数组按照字符串的操作方式,只需要字符数组的首地址就可以操作一个字符串。指针:存储地址,存储字符数组的首地址,这个指针就可以当作数组名操作(指针可以代表数组名,即数组首地址)。

char buf[10] = "hello";
char * p = buf;	//p就代表字符数组首地址
scanf("%s",p);	//输入字符串到buf数组中
//for循环的特色用法
for(int i=0; buf[i] != '\0';i++);	//直接找字符数组中'\0'的下标位置
  • 常量字符串
    “hello” :常量字符串。只要在程序中定义常量字符串,则常量字符串也会存储在内存中。定义字符串时,常量字符串就表示常量字符串的首地址,只能通过指针来存诸常量字符串的首地址

    char * p = "常量字符串";	//p就是存储了这个常量字符串的首地址
    

(8)多级指针与指针数组

  • a、多级指针

    • 一级指针变量存储普通变量的地址

    • 二级指针变量存储一级指针变量的地址

    • 三级指针变量存储二级指针变量的地址

      int a 10;
      int * p = &a;		//p存储&a
      int * * q = &p;		//q存储&p
      int ** * z = &q;	//z存储&q
      printf("%ld\n",sizeof(p))	//8
      printf("%ld\n",sizeof(q))	//8
      printf("%ld\n",sizeof(z))	//8
      // *z == q
      // **z == *q == p
      // ***z == **q == *p == a
      
      image-20230308093647365

b、指针数组
是一个数组,数组中的每个元素是指针类型,可以说成是”指针的数组”,“指针”修饰这个数组。二级指针可以和指针数组相互替换使用。指针数组还是数组,数组名表示数组首地址,即第0个元素的地址

  • 定义

    指针类型 数组名[ 元素个数 ];
    

    指针类型:指向数据类型 *
    例如:

    //存储5个整型的地址,在64位系统中,每个元素的大小都为8字节
    int * a[5];	//a数组有5个元素,每个元素都是指针,指针存储int类型的地址
    char *arr[4] = {"hello", "world", "shannxi", "xian"};  //arr就是一个指针数组,它有4个元素,每个元素是一个char *类型的指针,这些指针存放着其对应字符串的首地址。
    

(9)数组指针和二维数组

a、二维数组
数组的每一个元素也是一个数组,就把这种数组叫做二维数组。二维数组中每个数组元素是一个一维数组,是一维数组的集合;数组元素是一维数组,是普通数据的集合

一维数组元素类型 二维数组名[二维数组元素个数][一维数组元素个数]

b、数组指针
是一个指针,存储地址,用于存储整个数组的地址,指针类型为数组地址类型,指向整个数组。

  • 定义

    元素类型 (*指针变量)[元素个数];
    

    例如:

    int (*p)[5];	//p是一个指针,存放int[5]这种数组的地址
    //具体使用案例
    int a[3][5];
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<5;j++)
        {
            a[i][j]=1;
        }
    }
    // *(*(a+i)+j);  //可用于访问数组中元素
    int (*p)[5] = a ;	//数组指针,a == &a[0],表示a[0]这个一维数组的地址
    for(int i=0;i<3;i++)
    {
    	for(int j=0;j<5;j++)
        {
            printf("%d ",*(*(p+i)+j));
        }
        printf("\n");
    }
    

image-20230308183445906

说明:当a+1时:移动二维数组元素的大小;当a[0]+1时:移动一维数组的元素大小。

3、指针在函数的使用

(1)指针在函数中的定义使用

(2)指针作为函数的参数使用
函数的形式参数为指针(地址),在调用时传递地址,可以得到对应空间的地址,可以通过地址访问对应内存空间。

  • 定义

    返回类型 函数名( 指向类型1 *指针变量名1, 指向类型2 *指针变量名2, ... )
    {
        函数体;
        return 返回值;
    }
    
  • 调用

    函数名( 地址1, 地址2, ... );
    

    注意
    如果函数的参数是数组类型,在调用时实参就需要数组,但是对于数组而言,实参的添加只能是数组名,形式参数的指针就自动退化为对应的指针类型

    例如:

    //函数中形参的指针会自动退化
    //退化前          退化后
    int a[10]   -->  int *a
    int *a[10]  -->  int **a
    int a[3][5] -->  int (*a)[5]
    

(3)指针类型作为函数的返回值
函数的返回值是指针:也叫做指针函数,函数的结果为一个数据的地址

指向类型 *函数名()
{
    函数体;
}

(4)函数指针
在执行程序时,函数在使用时也是存储在内存中(函数就有存储的地址),所以在调用函数时也是找到函数的地址然后执行函数的内容。函数是存在地址的,函数名就表示函数的地址。函数指针:用于存储函数的地址的指针变量。由于函数的返回值,参数列表的类型都可能各不相同,一个函数都由这几个部分影响,对于函数而言,不同返回值,不同参数列表代表不同的函数类型。

  • 定义

    返回类型 (*函数指针名)(参数列表);
    
  • 调用

    函数指针名(实参列表);	//调用函数指针对应的函数
    

    例如:

    //函数add,求和
    int add(int a,int b)
    {
    	return a + b;
    }
    //函数指针定义
    int (*p)(int a,int b);	//p是函数指针名
    p = add;	//或者直接写: int (*p)(int a,int b) = add;
    printf("%d\n",p(10,20));
    

4、其他指针

(1)空指针
在内存中,地址 0 是不可用地址,在定义变量或分配空间时也不可能申请到 0 这个地址(系统规定0x0这个地址不允许任何程序进行访问),通常就使用 0 这个地址来作为定义指针之后的初始值,把 0 这个地址叫做空指针:NULL == 0x0
例如:

int *p = NULL;

(2)野指针
指针存储的地址不明确(指向的空间不清楚),这个地址对应的空间是否能够在程序中操作不清楚。
例如:

int *z;
*z = 10;	//错误,不确定操作哪个空间

(3)万能指针( void * )
int *,float *,都只能存储对应类型的地址。void:空类型,不存在这个类型的大小。指针大小是固定的4字节(32位系统)或8字节(64位系统)。void *:空类型指针,也叫做万能指针,一种指针类型,可以存放任意类型的地址,也可以把指针值赋值给任意其他类型的指针变量,注意:不能用 * 取对应地址的值

int *p;
int a;
void *q = &a;
float b;
q = &b;
p = q;
//不能用*取对应地址的值,因为不知道所存数据所占字节的大小,故不能取出!!!
*q;	//错误
*p;	//正确,按int类型(4个字节)去取数据

5、define和typedef

(1)typedef
指进行类型替换,用一串字符来表示代替已有的类型。

  • 语法

    typedef 被替换的类型 新命名;	 //把被替换的类型,用新命名来表示
    

    例如:

    typedef int * int_p;
    int_p p1,p2;  //p1,p2都是指针
    //函数指针类型替换
    char * ok(int a,int *p)
    {
        return NULL;
    }
    typedef char * (* func_p )(int a,int *p);	//用func_p代表这个函数指针
    func_p m;	//定义函数指针m
    m = ok;
    

(2)define
内容的替换(原样替换),是什么内容就原样替换出来,也叫做宏定义

#define 定义的名字 被替换的内容
  • #define可以带参数
    使用#define进行替换时,定义的名字还可以携带一些参数,在被替换的内容中进行使用。

    #define 名字(参数) 被替换的内容(使用参数)
    

注意
带的参数一定要加括号(),否则极其容易因为运算符优先级的问题导致运算出错!!!

例如:

#define N 10	//表示用N来替换10,只要之后使用N就是使用10
//注意
#define p_int int *
p_int a,b;	//等价于 int * a,b;	//其中a为指针,b为整型
//#define带参数
#define ADD(A,B) (A)+(B) //注意参数加括号
ADD(1,2);	//结果为3
#define MAX(a,b) a > b ? a : b
Max(2,6);	//结果为6
//参数不加括号,错误演示!
#define MUL(C,D) C * D	//此时参数未加括号
MUL(1+2,3+4);	//此时结果不是3*7=21,而是 1+2 * 3+4 = 11

6、存储类型关键字

C语言内存分区示意图从栈区到代码区,是从高地址到低地址。栈向下增长,堆向上增长。

内存位置描述权限
栈区栈区(stack):向下增长(地址由高到低生长)、先进后出,由编译器自动分配释放,存放:局部变量、const修饰的局部变量、形参、返回值可读可写
堆区堆区(heap):向上增长(地址由低到高生长)、进出随意,由程序员分配内存和释放,通过调用函数:malloc()和free()可读可写
全局(静态)区分为未初始化和已初始化全局/静态区,如:全局变量、static修饰的静态变量可读可写
常量区用于初始化变量的常量,const修饰的全局变量、如字符串“ABCD”、数字123等只读
代码区存放程序的代码只读
image-20230308184641587
  • 存储类型关键字
    定义的变量的位置

    • auto
      在局部变量中使用,表示自动存储管理(自动分配,自动释放),表示在栈区存储局部变量,如果局部变量没有添加存储类型,默认添加auto

      (auto) 数据类型 变量名;
      
    • static
      静态数据类型,表示把数据存储在数据段中的静态区(也叫全局区)。static修饰局部变量:表示局部变量不再是默认的存储类型(auto),而是存储到静态区。变量的生命周期不再是语句块结束而结束,生命周期延长到整个程序结束,作用域不变

      注意:

      • 如果遇到多次创建同一个静态变量,不会重复定义初始化,使用之前创建的静态变量!
      • static修饰全局变量:只能在本文件内使用!
      • static修饰的变量在定义时若不赋初值,默认值为0
    • register
      寄存器数据类型,把数据存储到cpu中的存储单元,在执行程序时,如果能够存储到cpu就进行存储,若cpu已经不能分配,则当作普通存储类型auto存储。

    • extern
      提供一个全局变量的引用,在其他文件中定义一个变量,现在在当前文件要使用其他文件定义的变量,需要把当前文件的变量名指向之前定义过的全局变量存储位置。
      注意

      • 定义全局变量时,即使不加extern关键字,编译器也会默认添加extern

      • extern修饰的全局变量不允许赋值,只能读!

        //在文件1.c中,定义全局变量 a = 10
        int a = 10;
        //在文件2中,引用 a
        extern int a;	//正确
        extern int a = 20;	//错误,不允许赋值!!!
        
    • const
      修饰变量,表示变量不可修改。

      int a = 10;
      const int b = 20;
      b = 30;	//错误,定义变量使用const修饰,不能修改
      const int * p;	//表示*p不能修改,但是p这个指针可以修改
      *p = 30;	//错误,p是指针可以修改,但是*p不能修改
      p = &a		//正确
      int * const q = &a;	//表示指针变量不能修改,但是*q可以修改
      q = &b;		//错误,不能修改指针q
      *q = 20;	//正确,指针指向的空间值可以修改
      int const * const z = &a;	//指针z和*z均不能修改
      z = &b;		//错误,不能修改为其他地址
      *z = 20;	//错误,z存储地址后,不能修改这个地址对应空间的值
      

7、自定义类型(构造类型)

​ 有些数据的表示不能够用单一的数据类型就能表示出来,C语言中就赋予类型的扩展型,可以按照c语言允许的方式去自己添加类型,这种叫做构造类型

(1)结构体类型(struct)
把基本数据类型复合在一起构成新类型,结构体类型就是由多个部分构成,共同组合表示一个完成的信息内容。

  • 声明结构体

    struct 结构体名
    {
        数据类型1 成员名1;
    	数据类型2 成员名2;
    	数据类型3 成员名3;
        ......
    };
    
  • 定义结构体变量

    struct 结构体名 变量名;
    
  • 结构体变量的使用
    结构体变量是一个整体,包含了多个成员,需要单独对构体变量的成员进行操作。

    • .成员运算符,用于访问结构体变量中的某个成员

      结构体变量名.成员名	//访问结构体变量中某个成员
      
  • 结构体变量初始化

    struct 结构体名 变量名 = {};	//全部初始化,所有成员都要赋值
    struct 结构体名 变量名 = { .成员名1=1, .成员名2=2, .成员名3=3, ... };  //部分初始化,可以只对部分成员赋值
    
  • 使用结构体指针访问结构体变量

    struct 结构体名 *指针名;
    指针名->成员名	//通过结构体指针访问结构体变量中某个成员
    
  • 结构体类型的大小

    • 结构体变量的首地址,必须是结构体变量中的“最大基本数据类型成员所占字节数”的整数倍。(对齐)
    • 结构体变量中的每个成员相对于结构体首地址的偏移量,都是该成员基本数据类型所占字节的整数倍。(对齐)
    • 结构体变量的总大小,为结构体变量中“最大基本数据类型成员所占字节数”的整数倍(补齐)

    例如:

    struct Test1
    {
    	int b;
    	double c;	
    	long d;
    }Test1;
    printf("size = %d\n", sizeof(Test1));	//size = 12
    

    20201124153907744

(2)联合体(共用体)类型(union)
和结构体类似,也是多个成员复合的类型(多个成员组成),多个成员共用同一份内存空间,每个成员没有单独的空间。空间大小是最大成员的空间能够整除共用体中最大的数据类型在使用共用体时,同一时间只能使用一个成员,当前成员使用完,才能去使用其他成员

  • 声明联合体

    union 联合体名
    {
        数据类型1 成员名1;
    	数据类型2 成员名2;
    	数据类型3 成员名3;
    };
    

(3)枚举类型(enum)
在使用之前就已经把类型能够取值的范围一一列举出来,在定义类型变量时变量就只能从中取值,这种类型就叫做枚举类型。每个枚举元素可以用一个标识符来表示,也可以为它们指定一个整数值,如果没有指定,那么默认从 0 开始递增

  • 声明枚举类型

    enum 枚举名
    {
        成员1,
        成员2,
        成员3,
        .....
    };
    

    注意
    定义枚举变量,就只能在成员范围内取值,每个成员都是一个整数值。当没有指定值的枚举元素,其值为前一元素加 1

    enum DAY
    {
          MON, TUE, WED, THU, FRI=100, SAT, SUN
    };
    enum DAY day;
    //MON的值为0,TUE的值为1,WED的值为2,THU的值为3,FRI的值为100,SAT的值为101,SUN的值为102
    

8、字符串的相关函数

​ 字符串的拷贝:strcpy、strncpy,求字符串的长度:strlen,比较两个字符串是否相等:strcmp、strncmp

#include <string.h>
//拷贝字符串
char *strcpy(char *dest,const char *src);	// 把src所指向的字符串复制到dest
char *strncpy(char *dest,const char *src,size_t n);	//复制src字符串的前n个字符至dest
//求字符串的长度
size_t strlen(const char *s);
//比较两个字符串是否相等,返回值: 0表示相等,非0表示不相等
int strcmp(const char *s1,const char *s2);
int strncmp(const char *s1,const char *s2,size_t n);  //比较两个字符串前n个字符是否相等,返回值: 0表示相等,非0表示不相等
  • sizeof和strlen的区别

    sizeofstrlen
    sizeof运算符是用于计算一个变量或数据类型所占用的字节数。对于一个字符串,sizeof返回的是整个字符串所占用的字节数,包括末尾的空字符’\0’strlen函数是用于计算字符串中实际字符的数量,不包括字符串末尾的空字符’\0’,返回的是一个size_t类型的整数。strlen函数只能用于计算字符串中实际字符的数量,而不能用于计算其他类型的数组长度。如果要计算整型数组的长度,可以使用sizeof运算符

9、动态内存

​ 在C语言内存分配中,存在一段内存空间,由我们程序员自己来申请内存空间进行使用,申请多大,什么时候申请,什么时候释放都是通过我们写的程序决定,叫做动态内存(堆区)

  • 申请内存(malloc)

    #include <stdlib.h>
    void * malloc(size_t size);
    
    • size_t size :参数,申请的空间大小(字节)

    • void * :返回值,万能指针,申请的空间首地址,申请失败则返回 NULL

    例如:

    //在堆空间申请空间使用
    int *p = malloc( sizeof(int) * 10 );	//申请40个字节
    for(int i 0;i<10;i++)
    {
    	*(p+i) = 1;	//p[i] = 1;
    }
    //结构体使用
    struct stu * q = malloc( sizeof((struct stu) );
    strcpy(q->name,"xiaoming");
    printf("%s",q->name);
    
  • 释放内存(free)

    #include <stdlib.h>
    //释放已经申请的空间
    void free(void *ptr);
    
    • void *ptr :参数,万能指针,表示释放的空间首地址

10、多文件编程

​ 编写程序时,通常一个文件来编写程序,会造成文件中代码非常庞大不利于阅读,通常程序会使用多个文件来编写、编译,即多文件编程。

  • 编写头文件,进行声明。头文件存放各种声明内容,头文件一般命名为:xxx.h

    //头文件中内容
    #ifndef __TEST_H
    #define __TEST_H
    //各种函数声明
    #endif
    
  • 编译时,多个文件都进行编译合并成一个可执行程序,在多个文件中相互调用,在调用的文件使用声明

    #include "头文件"
    
  • gcc编译流程

    • 预处理
      处理宏定义,引入头文件等,处理#开头的内容

      gcc -E xxx.c	//生成 xxx.i 文件
      
    • 编译
      把C程序翻泽成汇编代码

      gcc -S xxx.c	//生成 xxx.s 文件
      
    • 汇编
      将汇编代码翻译为二进制机器码

      gcc -C xxx.s	//生成 xxx.o 文件
      
    • 链接
      将多个二进制文件链接在一起合并成一个可执行程序

      gcc xxx1.o xxx2.o xxx3.o	//生成 a.out 文件 
      
  • make工程管理器
    它是一个用于管理编译工程辅助作用的工具软件,make工具是根据文件中的描述来进行管理:makefile(MakeFile)文件,用 vim makefile 打开

    • makefile文件编写格式

      目标 : 依赖		//依赖可以没有,依赖也可以是另一个目标,先会执行另一个目标
      <table>命令/或动作	//table键,表示空格
      

      例如:

      all

      说明
      all:目标;1.c 2.c 3.c:依赖

    • 文件执行

      make all	//执行makefile文件中的目标all
      make clean	//执行makefile文件中的目标clean
      
    • 变量

      • 用户自定义变量

        变量名=
      • 使用变量

        $(变量名)
        

      例如:

      image-20230310102557420-1678415161131-1

      • 自动变量

        • $* :不包含扩展名的目标文件名
        • $@ :包含扩展名的目标文件名(推荐使用)
        • $+ :所有的依赖文件,从左至右,有可能重复
        • $^ :所有的依赖文件,从左至右,不重复
        • $< :第一个依赖文件
      • 隐藏规则

        %.o : %.c	//%:根据依赖文件 1.o 2.o 3.o来进行替换
            gcc -c $< -o $@
        

        例如:

        image-20230310104419305

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值