• 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/
  • 相关阅读:
    Oracle 与.NET Framework 数据类型映射
    mvc使用JsonResult返回Json数据(转)
    like参数替换
    jquery 等比缩放
    【linq to sql】已有打开的与此命令相关联的 DataReader,必须首先将它关闭
    脚本
    2012年计划——开始我的敏捷个人之行
    在Win7 64位机器上安装Oracle 10客户端以及PlSql
    词干提取算法Porter Stemming Algorithm解读
    开源搜索框架Lucene学习之分词器(2)——TokenFilter类及其子类
  • 原文地址:https://www.cnblogs.com/yejiaxing-01/p/15256892.html
Copyright © 2020-2023  润新知