读书笔记-Linux C 编程从基础到实践所有内容在
github/zxwtry/document/linux/201606/Linux C 编程从基础到实践
github链接:点击这里
024 vim三种工作模式 命令行模式(也叫普通模式,初始模式) 插入模式 底行模式 vim三种工作模式的切换: 命令行模式 --> 插入模式 "i" "a" "o" 插入模式 --> 命令行模式 "Esc" 命令行模式 --> 底行模式 ":" "/" "?" 底行模式 --> 命令行模式 "Esc" 025 vim常用命令 h <-- j v k ^ l --> ^ 一行开头,非空字符 $ 行尾 == "End" 0 行首 == "Home" G 最后一行开头,非空字符 nG 第n行开头,非空字符 w 光标向后移动一个单词 nw 光标向后移动n个单词 b 光标向前移动一个单词 nb 光标向前移动n个单词 e 将光标移动到本单词的最后一个字符 如果已经在最后一个字符,跳到下一个单词的最后一个字符 { 移动到前面的"{"处 } 移动到后面的"}"处 Ctrl+b 向上翻一页 Ctrl+f 向下翻一页 Ctrl+u 向上翻半页 Ctrl+d 向下翻半页 Ctrl+y 向上翻一行 Ctrl+e 向下翻一行 026 vim复制、粘贴常用命令 yy 复制光标所在整行 yw 复制光标所在单词 nyy 从当前行向下,总共n行,全部复制 nyw 从当前单词向后,总共n个,全部复制 p 粘贴 027 vim删除文本常用命令(向后:包含当前 向前:不包含当前) x 删除光标所在位置的字符 == "Delete" X 删除光标所在位置的前一个字符 nx 从当前字符向后(包含当前字符),总共n个,全部删除 nX 从当前字符向前(不包含当前字符),总共n个,全部删除 dw 删除光标所在位置的单词 ndw 从当前单词向后,总共n个,全部删除 d$ 从当前字符向后(包含当前字符),一直删除到行尾 d0 从当前字符向前(不包含当前字符),一直删除到行首 dd 删除光标所在行 ndd 从当前行向下,总共n行,全部删除 nd+方向上键 删除当前行,向上再删n行,总共n+1行 nd+方向下键 删除当前行,向下再删n行,总共n+1行 028 vim其他常用命令 r 替换光标所在位置的字符 例如rz是指将光标所在位置的字符替换为x R 替换光标所到之处的字符,知道按下"Esc"键为止 u 撤销上一次的操作 U 取消对当前行所做的所有改变 . 重复执行上一次的命令 ZZ 保存文档后退出vim编辑器 % 符号匹配功能,在编辑时若输入"%(",则系统会自动匹配相应的")" 书上面是这么写,不过常用的是: 当光标在"("字符上的时候,命令行模式,%,会自动跳到匹配的")" 029 vim命令行工作模式切换到插入工作模式 i 从光标所在的位置,开始输入字符 I 光标所在行的行首,开始输入字符 a 从光标所在位置的下一个字符,开始输入字符 A 从光标所在行的行尾,开始输入字符 o 增加一行,光标到新增行的开头 O 当前行上面增加一行,光标到新增行的开头 030 vim底行工作模式 x 保存文件并退出vim(exit) x! 强制保存文件并退出vim w! 对于只读文件,强制保存修改的内容,但不退出vim E 在vim中穿过年间新的文件,并为文件命名 N 在本vim窗口中打开新的文件 w filename 另存为filename文件,不退出vim w! filename 强制另存为filename文件,不退出vim r filename 读入filename指定文件内容插入到光标位置(read) 插入的位置是,下一行开始输入文件的内容 原本的内容,自动向下移动 set nu 在vim的每行开头处,显示行号 s/pattern1/pattern2/g 当前行,g全部,没有g 替换第一个 %s/pattern1/pattern2/g 所有行,全部替换 num1,num2s/pattern1/pattern2/g num1行到num2行 031 vim配置 a, 进入vim --> :echo $VIM 查看vim配置文件所在位置 b, gcc可能需要安装一些常用的头文件和库文件 apt-get install build-essential c, gcc对C语言的处理过程 1, 预处理 处理头文件和宏定义 eg.#define #include #if等 2, 编译 gcc先检查代码的规范性,确认没有语法错误 gcc再将C语言代码编译成汇编语言代码 3, 汇编 gcc将刚刚得到的汇编语言用于输入, 产生具有.o扩展名的目标文件 4, 链接 在本阶段中各目标文件被gcc放在可执行文件的适当位置 该程序引用的函数也放在可执行文件中(对使用共享库的程序稍有不同) d, 一个简单C语言程序的处理过程 1, 预处理阶段: 这个阶段过后,会生成预处理文件(*.i) gcc把stdio.h头文件的部分内容插入到了文件中 2, 编译阶段: gcc会生成汇编代码文件hello.s 3, 汇编阶段: gcc把编译阶段生成的".s"文件转换成目标文件 4, 链接阶段: 最关键因素是函数库 从源文件中可以看到在其中并没有定义"printf"的函数实现 且在预编译中包含的"studio.h"中也只有该函数的声明, 而没有定义函数的实现 这是因为系统把这些函数的实现都放到名为libc.so.6的库文件中 没有特别指定时,gcc会到系统默认的搜索路径"/usr/lib"下查找 也就是链接到libc.so.6函数库中去,这样就能调用函数"printf" 以上就是链接的作用。 函数库有静态库和动态库两种: 静态库: 编译链接时,将库文件的代码全部加入可执行文件中, 因此生成的文件比较大,但运行时不再需要库文件, 其后缀名通常".a"。 动态库: 动态库与之相反,在编译链接时并没有将库文件的代码加入 可执行文件中,而是在程序执行时加载库,这样可以节省系统的 开销。一般动态库的后缀名为".so",如前面所述的libc.so.6就是 动态库。 gcc在编译时默认使用动态库 033 gcc中文件后缀 C语言代码: *.c 预处理文件: *.i 汇编代码文件: *.s 目标文件: *.o / a.out 032 gcc的基础使用方法 gcc [选项] 文件名 -c 仅对源文件进行编译,不链接生成可执行文件。 在对源文件进行查错或只需产生目标文件时,使用。 -o filename 将经过gcc处理过的结果保存为filename。这个处理文件可以是 预处理文件、汇编文件、目标文件或者最终的可执行文件。假设 被处理的源文件为file1,如果这个选项被忽略,那么生成的 可执行文件的默认名称为a.out;目标文件的默认名为file1.o; 汇编文件的默认名为file1.s;生成的预处理文件发送到标准输出设备stdout -g / -gdb 在可执行文件中加入调试信息,方便进行程序的调试。如果使用 “-gdb”选项,表示加入gdb扩展的调试信息,以便使用gdb来进行调试。 -O[0,1,2,3] 对生成的代码进行优化,括号中的部分为优化级别,默认的情况为 2级优化,0为不优化。优化和调试通常不兼容,同时使用"-g"和"-O" 选项经常会使程序产生奇怪的运行结果,所以不要同时用"-g"和"-O" -Idir 将dir目录加到搜索头文件的目录列表中去,并优先于gcc缺省的搜索目录。 在有多个"-I"选项的情况下,按命令行上"-I"选项的前后顺序搜索, dir可使用相对路径 -Ldir 将dir目录加到搜索"-L"选项指定的函数库文件的目录列表中去,并优先于 gcc缺省的搜索目录。在有多个"-L"选项的情况下,按命令行上"-L"选项的 前后顺序搜索,dir可使用相对路径 -Iname 在链接时使用函数库name.a,链接程序在"-Ldir"选项指定的目录下,以及 "/lib","/usr/lib"目录下寻找该库文件。在没有使用"-static"选项时, 如果发现共享函数库name.so,则使用name.so进行动态链接 033 gcc支持的与C语言相关的输入文件类型 .c C语言源程序,可以被gcc预处理、编译、汇编、链接 .C .cc .cp .cpp .c++ .cxx C++语言源程序,可以被gcc预处理、编译、汇编、链接 .i 预处理后的C语言源程序,可以被gcc编译、汇编、链接 .ii 预处理后的C++语言源程序,可以被gcc编译、汇编、链接 .s 预处理后的汇编程序,可以被as汇编、链接 .S 未预处理的汇编程序,可以被as预处理、汇编、链接 .h 头文件,不进行任何操作 .o 编译后的目标文件,传送给ld .a 目标文件库,传送给ld 034 gcc的应用实例 建立三个文件c001_main.c c001_sum.c c001_sum.h 编译 gcc c001_main.c c001_sum.c -o c001 github: https://github.com/zxwtry/store/tree/master/c/201606 035 Linux C语言的调试工具 gdb file 装入想要调试的可执行文件 kill 终止正在调试的程序 list 列出产生执行文件的部分源代码 next 执行一行源代码但不进入函数内部 step 执行一行源代码而且进入函数内部 run 执行当前被调试的程序 quit 退出gdb watch 动态坚实一个变量的值 make 不退出gdb而重新产生可执行文件 call name(args) 调用并执行名为name、参数为args的函数 return value 停止执行当前函数,并将value返回给调用者 break 在代码里设置断点,使程序执行到此处被挂起 通常来说,gdb只需要使用一个参数,标准格式如下: gdb <可执行文件> 如果程序运行时产生了错误,会在当前目录下产生核心内存映像core文件, 可以在指定执行文件的同时为可执行程序指定一个core文件: gdb <可执行文件名> core 除此以外,还可以为要执行的文件指定一个进程号 gdb <可执行文件名> <进程号> 以下是使用gdb来为一个生成的可执行文件指定进程号的过程,gdb首先 会寻找一个文件名为2000的文件,如果找不到,则把调试程序的进程号(PID) 设成2000,整个执行过程如下: gdb exam201hello 2000 当gdb运行时,把任何一个不带选项前缀的参数都作为一个可执行文件、 core文件或被调试程序相关联的进程号。不带任何选项前缀的参数和 前面加了"-se"或"-c"选项的参数效果相同。gdb把第一个前面没有选项说明 的参数看作前面加了"-se"选项,也就是需要调试的可执行文件并从此文件 里读取符号表,如果有第2个前面没有选项说明的参数,将被看作是跟在"-c" 选项后面,也就是需要调试的core文件名。 如果不希望看到gdb开始的提示信息,可以用"gdb-silent"执行调试工作, 通过更多的选项,开发者可以通过自己的喜好定制gdb的行为。 输入"gdb -help"或"-h"可以得到gdb启动时的所有选项提示。gdb命令行中 所有参数都按照排列的顺序传给gdb,除非使用了"-x"参数 gdb中常用的参数选项 -s filename 从filename指定的文件中读取要调试的程序符号表 -e filename 执行filename指定的文件,并通过与core文件进行 比较来检查正确的数据 -se filename 从filename中读取符号表并作为可执行文件进行测试。 -c filename 把filename执行的文件作为一个core文件 -c num 把数字num作为程序号和调试的程序进行关联, 与attach命令相似 -command filename 按照filename指定的文件中的命令执行 gdb命令,在filiename指定的文件中存 放着一系列的gdb命令,就像一个批处理。 -d path 指定源文件的路径,把path加入到搜索源文件 的路径中 -r 从符号文件中一次读取整个符号表,而不是 使用默认的方式首先调入一部分符号,当需要 时再读入其他部分,这会使gdb的启动较慢, 但可以加快以后的调试速度。 gdb运行模式的选择 -n 不执行任何初始化文件中的命令(一级初始化文件被称为 .gdbinit)。一般情况下在这些文件中的命令会在所有的命令 行参数都被传给gdb后执行 -q 设定gdb的运行模式为"安静模式",可以不输出介绍和版权 信息,这些信息在"批模式"中也不会显示 -batch 设定gdb的运行模式为"批模式",gdb在"批模式"下运行 时,会执行命令文件中的所有命令,当所有命令都被 成功执行后gdb返回状态0,如果在执行过程中出错,gdb 返回一个非零值 -cd dir 把dir作为gdb的工作目录,而非当前目录(gdb缺省时,把 当前目录作为工作目录) 036 gdb应用实例 a, 运行"gdb+待调试的可执行文件名称"命令来启动调试 b, 使用"b"快捷键在程序开始处设置断点,然后使用"run"开始调试 c, 使用"n"快捷键即可执行下一条语句,期间还可以使用其他命令来 观察相应的变量运行情况 d, 运行过程如下: gdb c002 //启动gdb对c002进行调试 b main //在main函数处设置一个断点 run //开始调试 n //执行下一条语句 037 Linux C语言的项目管理工具make 一个c语言的工程项目常常由多个文件组成,为了对多个文件进行管理 和处理,可以使用make项目管理器 C语言的编译过程分为编译、汇编、链接阶段,其中编译阶段仅仅检查 语法错误以及函数和变量是否被正确地声明了,在链接阶段则主要完成 函数链接和全局变量的链接,因此,那些没有改动的源代码根本不需要 重新编译,只要把他们重新链接进去就可以了,所以用户需要有个项目 管理器能够自动识别更新的文件代码,而不要重复输入冗长的命令行, 这时,make工程管理器就应运而生。 make工程管理器是个"自动编译管理器","自动"是指make能够根据文件 时间戳自动发现更新过的文件而减少编译的工作量,同时其通过读入 makefile文件的内容来执行大量的编译工作。用户只需编写简单的编译 语句就可以了,大大提高实际项目的工作效率。 038 make的基本结构 makefile是make项目管理器中使用的配置文件,其通常由以下几个部分 组成。 a, 目标体: make项目管理器生成的目标文件(target)或者可执行文件 b, 依赖文件: make项目管理器创建目标体所需要的文件 (dependency file),通常是C语言文件、C语言的头文件等 c, 相关操作命令: make项目管理器使用依赖文件来创建目标体 所需要的命令(command),这些命令必须以制表符(Tab)开头 一个标准的makefile文件如下: 两个makefile文件分别命名为hello.c和hello.h的文件经过编译 生成目标体hello.o,执行的命令为gcc编译指令: gcc -c hello.c 实际的应用代码如下: #The simplest example hello.o:hello.c hello.h gcc -c hello.c -o hell.o 此时可以使用make项目管理器了,其标准调用格式如下: make target 运行时make项目管理器会自动读入makefile文件,找到相关的依赖 文件并执行对应target的command语句,可以看到以上makefile的 文件输出结果如下: make hello.o 可以看到,make项目管理器执行了"hello.o"对应的命令语句,并 生成了"hello.o"目标体 039 make的变量(一个较为复杂的makefile) 在这个makefile中有3个目标体(target),分别为exam1、exam2.o、 exam3.o,其中第一个目标体的依赖文件就是后两个目标体,如果用户 使用命令"make exam1",则make管理器找到exam1目标体开始执行。 实际的应用代码如下: exam1:exam2.o exam3.o gcc exam2.o bar.o -o myprog exam2.o:exam.c exam2.h head.h gcc -Wall -O -g -c exam2.c -o exam2.o exam3.o:bar.c head.h gcc -Wall -O -g -c yul.c -o exam3.o 在执行以上makefile文件时,make项目管理器会自动检查相关文件的 时间戳,首先先检查"exam2.o"、"exam3.o"和"exam1"3个文件的时间戳 之前,它会向下查找那些把"exam2.o"或"exam3.o"作为目标文件的 时间戳。例如,"exam2.o"的依赖文件为"exam2.c"、"exam2.h"、"head.h"。 040 还是有非常多的不理解的地方,在网上找到wiki.ubuntu.org.cn这个 网站,貌似东西比较多,简单看一下: a, makefile的好处是:"自动化编译",一旦写好,只需要一个make 命令,整个工程完全自动编译。make是个命令工具,是一个解释 makefile中指令的命令工具 b, 程序的编译和链接:c语言首先要把源文件编译成中间代码文件, UNIX下是.o文件,即Object File,这个动作叫做编译(compile)。 然后再把大量的Ojbect File合成执行文件,这个动作叫做链接 (link)。 c, 编译时,编译器需要的是语法的正确,函数与变量的声明的正确。 对于后者,通常是你需要告诉编译器头文件的所在位置(头文件 中应该只是声明,而定义应该放在C/C++文件中),只要所有的 语法正确,编译器可以编译出中间目标文件。一般来说,每个 源文件都应该对应于一个中间目标文件(O文件或者OBJ文件) d, 链接时,主要是链接函数和全局变量,所以,我们可以使用这些 中间目标文件(O文件或OBJ文件)来链接我们的应用程序,编译器 并不管函数所在的源文件,只管函数的中间目标文件,在大多数 时候,由于源文件太多,编译生成的中间目标文件太多,而在链 接时,需要明显地之处中间目标文件名,这对于编译很不方便, 所以,需要给中间目标文件打个包,在Windows下这种包叫 "库文件"(Library File),即.lib文件,在UNIX下,是Archive File,也就是.a文件。 e, make命令执行时,需要一个makefile文件,告诉make命令如何去 编译和链接程序。一个示例程序,工程里面有8个c文件和3个 头文件,需要写一个makefile来告诉make命令如何编译和链接 这几个文件。规则是: 1) 如果这个工程没有编译过,所有的c文件都要编译并被链接 2) 如果这个文件的某几个c文件被修改,只编译被修改的c文件 ,并链接目标程序。 3) 如果这个工程的头文件被改变了,那么我们需要编译引用这 几个头文件的c文件,并链接目标程序。 f, make的规则 target ... : prerequistites ... command ... ... target可以是一个object file(目标文件),也可以是一个执行 文件,还可以是一个标签(label)。 prerequistes就是,要生成那个target所需要的文件或是目标。 command也就是make需要执行的命令。(任意shell命令) 就是文件的依赖关系,target这一个或多个的目标文件依赖于 prerequistes中的文件,其生成规则定义在command中。 prerequistes中如果有一个以上的文件比target文件要新的话, command所定义的命令就会被执行。这就是makefile的规则, 也是makefile中最核心的内容。 g, 一个makefile的示例 如果一个工程中有3个头文件和8个c文件,makefile文件如下: edit : main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o //如果后面.o问你教案比edit新,才会执行下面 cc -o edit main.o kbd.o command.o display.o\ insert.o search.o files.o utils.o main.o : main.c def.h cc -c main.c kbd.o : kbd.c defs.h command.h 041 回到书本,现在在P65 1, makefile 中有变量 2, 变量代替目标提、依赖文件、命令和makefile文件中的其他部分 3, 变量定义两种方式:递归展开方式和简单扩展方式 4, 递归展开方式:变量包含对其他变量的引用,在引用该变量的时候 ,一次性将全部内嵌变量展开。有严重缺点(CFLAGS=$(CFLAGS) -O )可能会导致无限循环。 5, 简单扩展方式变量的值在定义处展开,并且只展开一次,不包含任何对 其他变量的引用,从而消除变量的嵌套引用。 6, makefile的变量名不包含":"、"#"、"="以及空格结尾的任何字符 串。 7, 递归展开方式的定义格式为:VAR=var 简单扩展方式的定义格式为:VAR=var mkae中的变量格式:$(VAR) 8, 使用变量的makefile文件 OBJS = exam2.o exam3.o CC = gcc CFLAGS = -Wall -O -g exam1:$(OBJS) $(CC) $(OBJS) -o exam1 exam2.o:exam2.c exam2.h $(CC) S(CFLAGS) -c exam2.c -o exam2.o exam3.o:yul.c yul.h $(CC) $(CFLAGS) -c yul.c -o exam3.o 9, makefile中的变量:用户自定义变量、预定义变量、自动变量和 环境变量。 10, 预定义变量,常见如下 AR 库文件维护程序的名称 默认ar AS 汇编程序的名称 默认as CC C编译器的名称 默认cc CPP C预编译器的名称 默认"$(CC) -E" CXX C++编译器的名称 默认g++ FC Fortran编译器的名称 默认f77 RM 默认rm -rf ARFLAGS 库文件维护程序的选项 无默认值 ASFLAGS 汇编程序的选项 无默认值 CFLAGS C编译器的选项 无默认值 CPPFLAGS C预编译器的选项 无默认值 CXXFLAGS FFLAGS 在上面程序中,cc没有采用默认值,故"cc=gcc"单列 11, makefile常见的自动变量 $* 不包含扩展名的目标文件名称 $+ 所有的依赖文件,空格分开,出现的先后为序,可能有重 $< 第一个依赖文件的名称 $? 所有时间戳比目标文件晚的依赖文件,并以空格分开 $@ 目标文件的完整名称 $^ 所有不重复的依赖文件,空格分开 $% 如果目标是归档成员,则该变量表示目标的归档成员名称 12, 使用自动变量的makefile文件 OBJS = exam2.o exam3.o CC = gcc CFLAGS = -Wall -O -g exam1:$(OBJS) $(CC)$% -o $@ exam2.o:exam2.c exam2.h $(CC)$(CFLAGS) -c $< -o $@ exam3:yul.c yul.h $(CC)$(CFLAGS) -c $< -o $@ 13, 同名,自定义变量会覆盖环境变量 14, makefile规则:目标体、依赖文件及其之间关系 上面例子都显式指明makefile的规则关系,如下面语句 "$(CC)$(CFLAGS)-c $< -o $@",为了简化makefile的编写,make 工程管理还定义了隐式规则和模式规则。 15, 隐式规则 a, 常见的隐式规则目录 C编译:".c"变成".o" $(CC) -c $(CPPFLAGS) $(CFLAGS) C++编译:"cc"或".C"变成".o" $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) Pascal编译:".p"变成".o"$(PC) -c $(PFLAGS) Fortran编译:".r"变成"-o" $(FC) -c $(FFLAGS) b, 示例 OBJS= exam2.o exam3.o CC = gcc CFLAGS = -Wall -O -g exam1:$(OBJS) $(CC) $^ -o $@ 16, 模式规则:相同处理规则,处理多个文件 相关文件前必须用"%"标明。 OBJS = exam2.o exam3.o CC = gcc CFLAGS = -Wall -O -g exam1:$(OBJS) $(CC)$^ -o $@ %.o:%.c $(CC)$(CFLAGS) -c $< -o $@ 17,隐式规则仅能利用make默认的变量来进行操作。 模式规则能够引入用户的自定义变量 042 autotools的使用 1, 对于简单的项目,可以自行编写makefile文件 对于较大的项目,可以使用autotools来自动生成makefile 2, $sudo apt-get install autoconf 3, 生成makefile的过程: a, aclocal: 生成aclocal.m4,处理本地宏定义 b, autoscan: 给定目录(或当前目录),搜索源文件以寻找一 般的一致性问题并创建一个文件 "configure.scan",该文件就是接下来autoconf 要用到的"configure.in"原型 c, autoconf: 对configure.in脚本配置文件进行处理。 d, autoheader: 负责生成config.h.in文件。该工具通常会从 "acconfig.h"文件中复制用户附加的符号定义 e, automake: 其要用到的脚本配置文件是makefile.am。用户 需要自己创建相应的文件,然后利用automake.c 工具转换成makefilemin,此时运行configure 自动配置设置文件即可将该.in文件生成 makefile文件。 使用autotools工具生成makefile文件的流程图在store/files/ imgs/F005 4, 使用autotools生成的makefile文件,除了能完成编译操作外,还 可以完成的操作: a, make默认"make all",生成对应可执行文件 b, make install将生成的可执行文件安装到系统目录中 (/usr/local/bin目录)并添加对应的全局变量。 c, make clean清除之前所编译的可执行文件和目标文件 d, make dist将可执行文件和涉及的文件生成一个.tar.gz [注]有两页没有笔记,原因是之前的autotools没有理解清楚。 043 Linux中C代码的运行机制 1, Linux中的程序是磁盘上的可执行文件,内核调用一个exec函数将 这个可执行文件调入内存然后执行。这个程序的执行实例被称为 进程,在Linux中每个进程都对应一个位移的非负数字标示符,称 为进程ID。 2, 一个进程,终止的方式有8种: a, 在main函数中使用return语句返回; b, 在exit函数终止进程; c, 调用_exit_或者_Exit函数终止进程; d, 最后一个线程从其启动例程返回; e, 最后一个线程调用了pthread_exit函数; f, 调用abort函数; g, 接到一个信号并且终止; h, 最后一个线程对取消请求做出了响应。 a-e是正常终止一个进程 f-h是异常终止 3, 在Linux中,内核使程序执行的唯一方式是调用一个exec函数,进 程自愿终止的唯一方法是显式或者隐式调用_exit/_Exit,又或者 使用一个外部信号来使得该进程终止。 files/imgs/F006_Linux启动和终止一个应用程序的示意图 files/imgs/F007_Linux用户程序的运行过程 044 Linux中C代码的程序存储空间 1, 讨论的是C语言代码编译生成的可执行文件 2, 这些可执行文件的存储空间可以分为 a, 正文段:存放了处理器执行的机器指令 共享:包括shell,gcc在内的程序在存储器中只需一个副本 只读:防止程序的可执行代码被意外修改 b, 初始化数据段:(数据段),需要进行初始化的变量值 int counter = 0; //counter被初始化为0,然后存放在初始化数据段中 //通常这些变量是全局变量 //因为非全局变量会在调用时,再分配空间并且进行初始化 c, 非初始化数据段:(bss段)与初始化数据段相对应,用来 存放不需要初始化(初始化为0或者空指针)的变量 e, 栈:这个段用于存放自动变量以及每次函数调用时需要保存 的信息。 f, 堆:用于动态存储分配,这个段位于非初始化数据段和栈之 间,很多场合下,这个段和栈一起合称为堆栈段。 *, 对于一个可执行文件而言,通常还有若干其它类型的段,例 如包含了符号表的段,包含了gdb调试信息的段和包含了动态 共享库链接表的段等。但是,这些段并不会在进程调用的时 候被装入存储区中。 3, 使用size命令查看一个可执行文件的正文段、数据段和bss段的长 度信息,单位是字节。 4, files/imgs/F008_Linux中典型的段分配方式 045 Linux中C语言代码的出错处理 1, 在Linux中,如果调用的函数出现错误事件,往往会返回一个负值 ,并且此时整型变量errno常常会被设置为含有附加信息的一个值 2, 在errno.h文件中Linux定义了常用的错误常量 3, errno的定义如下,其可以是一个包含出错编号的整数,也可以是 一个返回出错编号指针的函数: extern int errno; 4, 对于errno而言,其有如下两条规则: a, 如果没有出错,则errno的值并不会被一个例程清除,因此可 以当函数返回值指明出错的时候才去检查error的值。 b, 任何一个函数都不会把errno设置为0。 5, Linux使用strerror和perror函数来打印相应的出错信息,对这两 个函数的标准调用格式说明如下: #include<string.h> char *strerror(int errnum); #include<Stdio.h> void perror(const char *msg); strerror函数的返回值是一个指向消息字符串的指针,这个消息 字符串即为出错信息的字符串;而peeror函数没有返回值,其输 出如下: "由msg指针指向的字符串"+":"+" "+"回车换行" 6, strerror和perror函数应用实例 分别调用strerror函数输出了一个EACCES错误对应的错误提示, 调用perror函数给出了一个带调用执行文件名的错误提示。 实例的应用代码如下: #include<string.h> #include<stdio.h> #include<errno.h> int main(int argc, char *argv[]) { fprintf(stderr, "EACCES:%s\n", strerror(EACCES)); errno = ENOSPC; perror(argv[0]); return 0; } 046 C语言的标准输入和输出函数 1, 标准输出函数printf #include<stdio.h> int printf(const char *format, ...); 2, printf函数的type参数 D 有符号十进制数 U 无符号十进制数 O 无符号八进制数 x 无符号十六进制数,使用小写 X 无符号十六进制数,使用大写 f 格式为[-]ddd.ddd的浮点数 e 格式为[-]ddd.ddde+dd的浮点数 E 格式为[-]ddd.dddE+dd的浮点数 g 使用f或者e中比较合适形式的浮点数 G 使用f或者E中比较合适形式的双精度值 c 单字符常数 s 字符串常数 p 指针,格式t:aaaa,其中aaaa为十六进制的地址,t为存储类型 n 无输出,但是在下一参数所指整数中写入字符串 % "%"字符 3, b, B, l, L用于type之前,说明整型d, i, u, o, x, X的char或者long转换。 4, flags是标记,printf函数的flags参数 - 左对齐 + 有符号,整值总是以正负号开始 空格 数字总是以符号或者空格开始 * 忽略 047 C语言的时间处理 #include<time.h> char *asctime(const struct tm *tm); char *asctime_r(const struct tm *tm, char *buf); char *ctime(const time__t *timep); char *ctime_r(const time_t *timep, char *buf); struct tm *gmtime(const time_t *timep); struct tm *gmtime_r(const time_t *timep); struct tm *localtime(const time_t *timep); struct tm *localtime_r(const time_t *timep, struct tm *result); time_t mktime(struct tm *tm); int gettimeofday(struct timeval *tv, struct timezone *tz); int settimeofday(const struct timeval *tv, const struct timezone *tz); 048 asctime函数:将参数timeptr所指的tm结构转换成真实世界所使用的 时间日期表示方法,返回字符串。"Wed Jun 30 21:49:08 1993/n" tm时间信息结构体说明如下: struct tm { int tm_sec; //秒 int tm_min; //分钟 int tm_hour; //小时 int tm_mday; //日期 int tm_mon; //月份 int tm_year; //年份 int tm_wday; //星期 int tm_yday; //从1月1日开始到当前日期编号 int tm_isdst; //夏令时标识符,实行夏令时的时候,为正 // 不实行夏令时的时候,为0 // 不明真相的吃瓜群众,为负 }; asctime_r函数:是asctime函数的一个拓展,提供了一个缓冲器buf用于存放返回 值,该缓冲区的长度不能低于26个字节。 ctime函数:将参数timep所指的time_t结构中的信息转换成真实世界所使用的 时间日期表示方法,然后将结果以字符串形态返回。此函数已经由时区转换 成当地时间,字符串的格式是"Wed Jun 30 21:49:08 1993/n"。若再调用 相关的时间日期函数,此字符串可能会被破坏。 ctime_r函数:和ctime函数功能相同,提供一个缓冲区用于存放返回值。 gmtime函数:将所指的time_t结构中的信息转换为真实世界所使用的时间日期 表示方法,然后将结果返回到tm结构体中。 gmtime_r函数: localtime函数:当地的目前时间和日期,其将参数timep所指的结构体中的信息 转换真实世界所使用的时间日期表示方法,然后返回。 localtime_r函数: mktime函数:将参数tm所指向的结构体数据转换为从1970年1月1日0时0分0秒开始 经历的秒数,然后返回。 gettimeofday:获取当前时间和时区信息,这个需要超级用户的权限,tv参数 用于指向存放返回时间信息的缓冲区,对其结构说明如下: struct timeval { time_t tv_sec; //秒 suseconds_t tv_usec; //微秒 }; tz用于存放相应的时钟信息,说明如下: struct timezone { int tz_minuteswest; //minutes est of Greewich int tz_dsttime; //type of DST correction }; settimeofday:用于设置当前时间和时区信息,其参数和使用方法参考gettimeofday 049 Linux时间实例 主要调用time localtime 和 asctime函数。处理过程是:通过gmtime函数获得 当前的秒数,然后通过asctime函数得到正常的显示格式。 #include<stdio.h> #include<time.h> int main() { time_t temp; char *wday[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; struct tm *p; time(&temp); printf("%s\n", asctime(gmtime(&temp))); p = localtime(&temp); printf("%d", p->wday); return 0; } 050 Linux程序运行的时间 #include<sys/time.h> #include<unistd.h> #include<stdio.h> int main() { struct timeval time1, time2; timezone timez; int i, tmp; gettimeofday(&time1, &timez); printf("%d\n", time1.tv_usec); for (i = 0; i < 10000; i ++) { tmp = i +1900; tmp = tmp * tmp; } gettimeofday(&time2, &timez); printf("程序使用时间:%d\n", (time2.tv_usec - time1.tv_usec)); return 0; } 051 Linux内存 malloc 申请连续内存,不初始化为0 calloc 为有指定流处理数和特定长度的对象分配内存,初始化为0 realloc 增加或者减少内存。如果是增加,可能会先复制到一块空闲区,再申请 新申请的内存,不初始化为0 free 释放内存 #include<stdlib.h> void *malloc(size_t size); void *calloc(size_t nobj, size_t size); void *realloc(void* ptr, size_t new_size); void free(void* ptr); // realloc中new_size指的是新申请的长度,而不是申请之后的总长度 // 如果realloc中ptr是一个null的话,realloc和malloc是一样的。 这三个函数都是通过sbrk系统调用来实现,系统调用的格式如下: #include<unistd.h> int brk(void *addr); void *sbrk(intptr increment); 052 系统调用 Linux系统提供内建函数用以完成对内核的调用,叫systemcall。在包syscall.h 里面,对应的是一系列数字,每一个数字对应system_call_table[]里面的一个 调用。Linux内核通过位于0x80中断来管理这些系统调用;当系统调用运行时, 对应数字和相关参数会存放到寄存器中。 具体实现:Linux在C库函数中实现了和系统调用名称一样的函数,用户写代码的 时候,能够直接调用这些函数。这些函数运行过程会调用内核完成系统。 Linux还提供一些库函数,可以实现一个或者多个系统调用,但是这些并不是系统 调用的入口。例如,atoi函数。 053 Linux内核,系统调用,库函数,用户函数之间的关系: Linux内核 被%s调用 系统调用 系统调用 被%s,%s调用 库函数,用户函数 库函数 被%s调用 用户函数 其中,用户进程包括:库函数和用户函数