zoukankan      html  css  js  c++  java
  • Unix/Linux系统编程-学习笔记-第二章

    第2章 编程背景

    2.1 Linux中的文本编辑器

    2.1.1 vim

    vim(Linux Vi和Vim Editor 2017)是Linux的标准内置编辑器。它是Unix原始默认vi编辑器的改进版本。与其他大多数编辑器不同,vim有3种不同的操作模式,分别是

    • 命令模式:用于输入命令
    • 插入模式:用于输入和编辑文本。
    • 末行模式:用于保存文件并退出。

    vim启动时,处于默认的命令模式,在该模式下,大多数键表示特殊命令。移动光标的命令键示例如下:

    • h:将光标向左移动一个字符
    • l:将光标向右移动一个字符
    • j:将光标向下移动一个字符
    • k:将光标向上移动一个字符

    在X-window中使用vim时,也可以通过箭头键来完成光标的移动。要输入文本进行编辑,用户必须输入i(插入)或a(追加)命令将vim切换到插入模式

    • i:切换到插入模式,插入文本。
    • a:切换到插入模式,追加文本。
      要退出插入模式,请按ESC键一次或多次。在命令模式下,输入“:”进入末行模式,将文本保存为文件或退出vim:
    • :w:写入(保存)文件。
    • :q:退出vim。
    • :wq:保存并退出。
    • :q!:不保存更改,强制退出。
      虽然许多Unix用户已经习惯了vim不同的操作模式,但是其他用户可能认为与其他基于图形用户界面(GUI)的编辑器相比,vim使用起来既不自然也不方便。以下类型的编辑器属于通常所说的所见即所得(WYSIWYG)编辑器。在WYSIWYG编辑器中,用户可以输入文本,用箭头键移动光标,和普通的文本输入一样。通常,通过输入一个特殊的meta键,接着输入一个字母键即可创建命令。例如:
    • Ctrl+C:中止或退出。
    • Ctrl+K:删除行到缓冲区。
    • Ctrl+Y:从缓冲区内容中复制或粘贴。
    • Ctrl+S:保存已编辑文本等。

    2.3程序开发

    2.3.1程序开发步骤

    (1)创建源文件;
    全局变量、局部变量、静态变量、自动变量和寄存器变量。
    图2.3

    (2)用gcc把源文件转换成二进制可执行文件。

    gcc t1.c t2.c
    

    (3)gcc的步骤:

    1. 将C源文件转换为汇编代码文件,即将.c文件转为.s文件。
    2. 将汇编代码转换为目标代码,即将.s文件转为.o文件。
      每个.o文件包含:
    • 一个文件头
    • 一个代码段
    • 一个数据段
    • 一个BSS段
    • 指针、数据和重定位信息
    • 符号表
    1. 链接。
    • 将.o文件的所有代码段组合成单一代码段。
    • 将所有数据段组合成单一数据段。
    • 将所有BSS段组合成单一bss段。
    • 使用.o文件的重定位信息调整组合指针、数据和bss段的偏移量。
    • 用符号表来解析各个.o文件之间的交叉引用。
      图2.4

    2.3.2 静态与动态链接

    这是创建二进制可执行文件的两种方式。

    1. 静态库的特点:
      链接器将所有必要的库函数代码和数据纳入a.out文件中。这使得a.out文件完整、独立,但通常非常大。
    2. 动态链接的优点:
    • 减小每个a.out文件大小;
    • 许多执行程序共享相同库函数;
    • 修改库函数无需重新编译源文件。
      动态链接所用的库称为动态链接库(DLL)。它们在Linux中称为共享库(.so文件)。动态加载(DL)库是指按需加载的共享库,可用作插件和动态加载模块。

    2.3.3 可执行文件格式

    1. 二进制可执行平面文件
    2. a.out可执行文件
    3. ELF可执行文件

    2.3.4 a.out文件的内容

    1. 文件头
    2. 代码段
    3. 数据段
    4. 符号表

    2.3.5 程序执行过程

    1. 读取a.out文件头,以确定所需总内存大小;
    2. sh从总大小中分配一个内存区给执行映像;
    3. 接着,sh放弃旧映像,开始执行新映像
    4. 执行从crt0.o开始,调用main()

    2.3.6 程序终止

    1. 正常终止
    2. 异常终止

    2.4 C语言中的函数调用

    2.4.1 32位GCC中的运行时堆栈使用情况

    1. 进程执行映像
      图2.7

    2. 每个CPU都有以下寄存器或同等寄存器:

    • PC(IP):指向CPU要执行的下一条指令。
    • SP(SP):指向栈顶。
    • FP(BP):指向当前激活函数的栈帧。
    • 返回值寄存器(AX):函数返回值的寄存器。
    1. main函数由crt0.o调用。
      图2.8

    2. 每个函数入口,编译后的代码完成如下功能:

    • 将FP压栈,在堆栈上保存CPU的FP寄存器。
    • 让FP指向保存的FP#建立栈帧。
    • 向下移动SP为堆栈上的自动局部变量分配空间。
    • 编译后的代码可能继续向下移动SP,在堆栈上分配一些临时工作空间,用temps表示。

    2.5 C语言程序与汇编代码的链接

    2.5.2 汇编代码说明

    三部分构成:

    1. 入口代码:又叫作prolog,它建立栈帧,在堆栈上分配局部变量和工作空间。
    2. 函数体代码:在AX寄存器中执行带有返回值的函数任务。
    3. 退出代码:又叫作epilog,它释放堆栈空间并返回到调用者。

    2.6 链接库

    2.6.1 创建静态链接库

    gcc -c mysum.c
    ar rcs libmylib.a mysum.o
    gcc -static t.c -L. -lmylib
    a.out
    

    2.6.2 创建动态链接库

    gcc -c -fPIC mysum.c
    gcc -shared -o libmylib.so mysum.o
    gcc t.c -L. -lmylib
    export LD_LIBRARY_PATH=./
    a.out
    

    2.7 makefile

    至此,我们已经可以用单个gcc命令来编译链接C语言程序的源文件了。为了方便,我们还可以使用包含所有命令的sh脚本。这些方案都有一个很大的缺点。如果只更改几个源文件,sh命令或脚本仍会编译所有的源文件,包括未修改的文件,这不但没有必要,而且浪费时间。一个更好的方法是使用Unix/Linux make工具(GNU make 2018)。make是一个程序,它按顺序读取makefile或Makefile,以自动有选择地执行编译链接。本节将介绍makefile的基础知识,举例说明它们的用法。

    2.7.1 makefile格式

    一个make文件由一系列目标项、依赖项和规则组成。目标项通常是要创建或更新的文件,但它也可能是make程序要引用的指令或标签。目标项依赖于一系列源文件、目标文件甚至其他目标项,具体描述键依赖项列表。规则是使用依赖项列表构建目标项所需的命令。

    目标项 依赖项列表
    target: file1 file2...fileN
    规则
    <tab> command1
    <tab> command2
    <tab> other command

    注:当你想在markdown文档里面输出<tab>或<br>时,可以像这样,在前面加上进行转义。

    2.7.2 make程序

    当make程序读取makefile时,它通过比较依赖项列表中源文件的时间戳来确定要构建哪些目标项。如果任何依赖项在上次构建后有较新的时间戳,make将执行与目标项有关的规则。假设我们有一个C语言程序包含3个源文件:

    1. type.h file: // 头文件

      int mysum(int x, int y) // types, constants,etc
    2. mysum.c file // C语言中的函数
      #include <stdio.h>
      #include "type.h"
      int mysum(int x, int y)
      {
          return x+y;
      }
      
    3. t.c file:
      #include <stdio.h>
      #include "type.h"
      int main()
      {
          int sum = mysum(123,456);
          printf("sum = %d
      ", sum);
      }
      
      通常,我们会使用sh命令

      gcc -o myt main.c mysum.c

      生成一个名为myt的二进制可执行文件。下面我们将使用makefile演示C语言程序设计的编译链接。

    2.7.3 makefile示例

    示例2.5:makefile。
    (1)创建名为mk1的makefile,包括:

    myt:type.h t.c mysum.c     # target: dependency list
        gcc -o myt t.c mysum.c  # rule: line MUST begin with a TAB
    

    在本示例中,生成的可执行文件名myt通常与目标项名称匹配。这允许make通过将目标项时间戳与依赖项列表中的时间戳进行比较,来决定稍后是否再次构建目标项。
    (2)使用mk1作为makefile运行make:make通常使用默认的makefile或Makefile,即当前目录中出现的makefile。它可以通过-f标志直接使用另一个makefile,如:

    make -f mk1

    make将构建目标文件myt,并将命令执行显示为:

    gcc -o myt t.c mysum.c

    (3)再次运行make命令,将会显示消息:

    make:'myt' is up to date

    在这种情况下,make不会再次构建目标,因为在上次构建后没有任何文件更改。

    (4)相反,如果依赖项列表中的任何文件有更改,make将再次执行rule命令。一种简单的文件修改方法是使用touch命令,修改文件的时间戳。那么,如果我们输入sh命令:

    touch type.h // or touch *.h, touch *.c, etc.

    make -f mk1

    make将重新编译链接源文件,以生成新的myt文件。
    (5)如果我们从依赖项列表中删除一些文件名,即使这些文件有更改,make也不会执行rule命令。读者可以尝试自行验证。
    可以看出,mk1是一个非常简单的makefile,它与sh命令的差别不大。但是,我们可以改进makefile,使之更加灵活和通用。
    示例2.6:makefile中的宏。
    (1)创建一个名为mk2的makefile,包括:

    CC = gcc            # define CC as gcc
    CFLAGS = -Wall      # define CLAGS as flags to gcc
    OBJS = t.o mysum.o  # define Object code files
    INCLUDE = -Ipath    # define path as an INCLUDE directory
    
    myt: type.h $(OBJS) # target: dependency: type.h and .o files
        $(CC) $(CFLAGS) -o t $(OBJS) $(INCLUDE)
    

    在makefile中,宏定义的符号——$(符号)被替换为它们的值。如$(CC)被替换为gcc,$(CFLAGS)被替换为-Wall等。对于依赖项列表中的每个.o文件,make首先会将相应的.c文件编译成.o文件。但是,这只适用于.c文件。由于所有.c文件都依赖于.h文件,所以我们必须在依赖项列表中显示地包含type.h(或任何其他.h文件)。或者,我们可以定义其他目标项来指定.o文件对.h文件的依赖关系,如:

    t.o: t.c type.h         # t.o depend on t.c and type.h
        gcc -c t.c
    mysum.o: mysum.c type.h # mysum.o depend type.h
        gcc -c mysum.c
    

    如果我们将上述目标项添加到makefile中,.c文件或type.h中的任何更改都将出发make重新编译.c文件。如果.c文件的数量很小,则会很有效。如果.c文件的数量很大,则会很繁琐。因此,有更好的方法将.h文件包含在依赖项列表中,稍后将进行展示。

    (2)以mk2作为makefile运行make。

    make -f mk2

    (3)按前面一样运行生成的二进制可执行文件myt。
    示例2.5和示例2.6的简单makefile足以编译链接大多数小型C语言程序。以下显示了makefile的一些附加功能。

    示例2.7:按名称编译目标。

    当make在makefile上运行时,通常会尝试在makefile中构建第一个目标。通过指定一个目标名称可以更改make的行为,从而make将设置特定的命名目标。以名为mk3的makefile为例,其中新功能以粗体字母突出显示。

    # ---------------- mk3 file --------------------------------
    CC = gcc                # define CC as gcc
    CFLAGS = -Wall          # define CLAGS as flags to gcc
    OBJS = t.o mysum.o      # define Object code files
    INCLUDE = -Ipath        # define path as an INCLUDE directory
    
    all: myt install        # build all listed targets: myt, install
    
    myt: t.o mysum.o        # target: dependency list of .o files
        $(CC) $(CFLAGS) -o myt $(OBJS) $(INCLUDE)
    
    t.o: t.c type.h         # t.o depend on t.c and type.h
        gcc -c t.c
    mysum.o: mysum.c type.h # mysum.o depend mysum.c and type.h
        gcc -c mysum.c
    
    install: myt            # depend on myt: make will build myt first
        echo install myt to /usr/local/bin
        sudo mv myt /usr/local/bin/     # install myt to /usr/local/bin/
    
    run: install            # depend on install, which depend on myt
        echo run executable image myt
        myt || /bin/true    # no make error 10 if main() return non-zero
    
    clean:
        rm -f *.o 2> /dev/null          # rm all *.o files
        sudo rm -f /usr/local/bin/myt   # rm myt
    

    读者可以通过输入以下make命令测试mk3文件:

    (1).make [all] -f mk3   # build all targets: myt and install
    (2).make install -f mk3 # build target myt and install myt
    (3).make run -f mk3     # run /usr/local/bin/myt
    (4).make clean -f mk3   # remove all listed files
    

    makefile 变量:makefile支持变量。在makefile中,%是一个与sh中的*类似的通配符变量。makefile还可以包含自动变量,这些变量在匹配规则后由make设置。自动变量规定了对目标和依赖项列表中元素的访问,从而用户不必显示指定任何文件名。自动变量对于定义一般模式规则非常有用。以下列出了make的一些自动变量。

    • $@:当前目标名
    • $<:第一个依赖项名
    • $^:所有依赖项名
    • $*:不包含扩展名的当前依赖项名
    • $?:比当前目标更新的依赖项列表
      另外,make还支持后缀规则,后缀规则并非目标,而是make程序的指令。我们通过一个例子来说明make变量和后缀规则。
      在C语言程序中,.c文件通常依赖于所有.h文件。如果任何.h文件发生更改,则必须编译所有.c文件。为了确保这一点,我们可以定义一个包含所有.h文件的依赖项列表,并在makefile中指定一个目标:
    DEPS = type.h       # list ALL needed .h files
    %.o: %.c $(DEPS)    # for all .o files: if its .c or .h file changed
        $(CC) -c -o $@  # compile corresponding .c file again
    

    在上面的目标中,%.o代表所有.o文件,$@设置为当前目标名称,即当前.o文件名。这样可以避免为单个.o文件定义单独的目标。
    示例2.8:使用make变量和后缀规则。

    # ---------------- mk4 file --------------------------------
    CC = gcc
    CFLAGS = -I.
    OBJS = t.o mysum.o
    AS = as     # assume we have .s files in assembly also
    DEPS = type.h   # list all .h files in DEPS
    
    .s.o: # for each fname.o, assemble fname.s into fname.o
        $(AS) -o $< -o $@   # -o $@ REQUIRED for .s files
    
    .c.o: # for each fname.o, compile fname.c into fname.o
    
    
    ———————————————————————————————————————————————————————————————— 转载麻烦附上本文链接和本声明,感谢! 博主<叶家星>博客园链接如下:https://www.cnblogs.com/yejiaxing-01/
  • 相关阅读:
    [ jquery 选择器 :hidden ] 此方法选取匹配所有不可见元素,或者type为hidden的元素
    剑指 Offer 03. 数组中重复的数字 哈希
    LeetCode 1736. 替换隐藏数字得到的最晚时间 贪心
    Leetcode 1552. 两球之间的磁力 二分
    Leetcode 88. 合并两个有序数组 双指针
    LeetCode 1744. 你能在你最喜欢的那天吃到你最喜欢的糖果吗?
    LeetCode 1743. 相邻元素对还原数组 哈希
    LeetCode 1745. 回文串分割 IV dp
    剑指 Offer 47. 礼物的最大价值 dp
    剑指 Offer 33. 二叉搜索树的后序遍历序列 树的遍历
  • 原文地址:https://www.cnblogs.com/yejiaxing-01/p/15256892.html
Copyright © 2011-2022 走看看