内存相关
开发环境
编译器
gcc编译器
什么是编译器:把人类能看的懂的语言翻译成机器能够看的懂的二进制语言的程序。
编译器
预处理器:把程序员编写的代码翻译成标准的C语言。
翻译器:把标准的C语言编程成二进制语言(没有入口)。
链接器:把若干个目标文件合并在一起生成可执行的二进制文件。
装载器:把可执行的二进制文件按照操作系统要求装载到内存并开始执行(程序员的自我修养)。
gcc编译器是GNU社区为了编译Linux内核开发的一套编译框架,不光能编译C语言,包括C++、java、Objective-C。
常用的参数
-E 只预处理,处理后的结果直接显示在屏幕上,如果需要保存需要加参数-ofile.i
-c 只翻译不链接,只生成目标文件file.o
-o 为编译出的结果重新命名。-o file
-Wall 尽可能多的产生警告,编译检查更严格。
-Werror 把警告当错误处理,如果有警告则不能再生成可执行程序。
-g 生成调试信息,要与gdb配合使用。
-S 生成汇编文件file.s
-D 在编译时定义宏
源码变成可执行程序的过程
1、编写源码,vim code.c
2、预处理生成以.i结尾的预处理文件,gcc -E code.c -o code.i
3、生成.s结尾的汇编文件,gcc -S code.i -> code.s
4、生成以.o结尾的目标文件,gcc -c code.s -> code.o
5、链接(ld)若干个.o文件生成可执行程序,gcc a.o b.o c.o -> a.out(elf)
GNU 编译框架
支持众多编译语言
C、C++、JAVA、Objective-C、Ada
支持各种编译平台
U L W
构建过程(build)
源代码变成可执行程序的过程a.c->a.out
编写.c文件
预处理 gcc -E a.c ->a.i
汇编 gcc -S a.i 生成 a.s
编译 gcc -C a.s 生成 a.o
链接 gcc a.o libstd.o 生成 a.out
gcc -v 查看编译器版本信息
Ubuntu 12.04 LTS
编译的版本号 gcc 版本4.6.3
编译的位数i686-linux-gnu
所支持的语言C、C++
头文件路径:/usr/include
文件类型
.c
源代码文件
.h
头文件
.i
预处理文件
.s
汇编文件
.o
目标文件
.a
静态库文件
.so
共享库文件
.gch
编译后的头文件
file.h file.gch (优先使用)
编译单个文件
-C
只编译不链接
-E
预处理
-S
汇编
-Wall
尽可能多的生成警告信息
-Werror
把警告信息当做错误处理
-g
生成调制信息
-x
指定要编译的语言
-pedantic
以ANSI标准检查语法,一旦出扩展的语法就会出现警告
编译多个文件
头文件的作用
声明外部变量和外部函数
定义宏常量、宏函数
类型设计、类型重定义
包含其他头文件
借助“头文件卫士”防止重复包含
防止头文件相互包含
定义c.h,把a.h和b.h中的共用部分移动到c.h
包含头文件和不包含头文件的区别
如果没有头文件,编译器会猜测函数的格式(返回值int,参数按实参的类型猜测)
#include""先在当前目录下查找,如果找不到再去系统指定的位置找
#include<>系统指定的位置去找头文件
编译器也可以指定头文件的查找路径 -I path
编译单个.c文件,然后再合并成可执行文件
gcc -c code.c->code.o
gcc code.o->a.out
编写Makefile脚本
预处理指令
#include
包含头文件
#define
定义宏常量或宏函数
#undef
取消宏定义
#if
判断
#else
当#if为假时再次判断
#elif
与#if配合使用
#endif
结束判断
#ifndef
判断没有定义宏
#ifdef
判断定义宏
##
连接两个标识符形成一个新的标识符
#
把参数转换成字符串字面值
#error
生成错误信息
#waring
生成警告信息
#pragma pack (2)
结构体超过2字节的,按照2字节对齐
#pragma GCC poison key
把key设置为病毒,禁止使用
#line
设置代码的行号
如何在命令行定义宏:gcc -D 宏名=数值
预定义的宏
__BASE_FILE__
当前正在编译的文件名
__FILE__
代码所在的文件名
__LINE__
获取行号
__FUNCTION__
获取函数名
__func__
__DATA__
获取日期
__TIME__
获取时间
__cplusplus__
用户判断编译器的语法是否是C++
与编译器相关的环境变量
vi~/.bashrc
C_INCLUDE_PATH
设置C语言头文件路径
CPATH
设置C语言头文件路径
export CPATH=/home/zhizhen
LIBRARY_PATH
设置库文件路径(编译时)
LD_LIBRARY_PATH
动态加载库文件路径(运行时)
添加环境变量:source ~/.bashrc
删除环境变量要关闭终端重新打开才有效
库
库(库文件):把若干个目标文件合并在一起形成的集合,就是代码的集合
分久必合(方便使用),合久必分(文件维护)
库文件的分类
静态库
调用者把所需的代码从静态库中直接拷贝到可执行文件中 .a
共享库(动态库)
调用者吧所需的代码先在共享库中确认,当执行时会把共享库和可执行文件一起加载,当需要执行共享库中的代码时直接从可执行文件中跳转过去 .so ,dll
静态库:编译出的静态库相对比较大,不易修改(静态库的代码会变,可执行文件要重新编译),但执行效率高
共享库:编译出的共享库相对比较小,容易修改(共享库发生改变,程序不用重新编译),但执行效率不高
代码库
长年代码的积累
静态库
创建静态库
gcc - c code.c ->code.o
ar -r libname.a code.o ...
调用静态库
静态库是需要与调用者一起编译(拷贝)
直接调用(调用者与库在同一目录下)
gcc hello.c libname.a
设置LIBRARY_PATH,配合-lname
vi ~/.bashrc
export LIBRARY_PATH=$LIBRARY_PATH:/home/zhizhen
gcc hello.c -lname
-Lpath配合-lname
gcc hello.c -Lpath -lname
ar命令
-r 生成静态库
-q 往静态库中追加目标文件
-d 从静态库中删除目标文件
-t 显示静态库中所有的目标文件
-x 把静态库展开成目标文件
共享库
创建共享库
-fpic 位置无关,代码段的地址都使用的是相对位置
gcc -fpic -c code.c->code.o
gcc -shared -o libname.so code.o ...
调用共享库
与调用静态库的库方法一致
共享库的运行
调用共享库的可执行文件,运行时需要共享库一起加载并执行
运行时查找共享库需要从LD_LIBRARY_PATH指定
export LD_LIBRARY_PATH=$LD_LIBRARY:~/math/
共享库相关命令
-fpic
小模式:生成的位置无关目标文件相对较小,速度较快,但只有个别平台支持
-fPIC
大模式:生成的位置无关目标文件相对较大,速度较慢,基本所有平台都支持
ldd
查找可执行文件所依赖的共享库文件
ldconfig
LD_LIBRARY_PATH环境变量所配置的路径会记录到ld.so.conf,每次开机时会共享库加载到ld.so.cache文件中,以此来提高共享库的运行速度,ldconfig可以重新生成ld.so.cache文件,而不用等到开机
sudo ldconfig
动态加载共享库
在编译时不再查找共享库,在运行时才去查找并加载共享库
要依靠环境变量
#include<dlfcn.h>
打开共享库
void *dlopen(const char *filename,int flag);
filename:库名或路径,如果只有库名则去LD_LIBRARY_PATH环境变量指定的位置查找
flag:
RTLD_LAZY 延时加载,使用时才加载
RTLD_NOW 立即加载,程序执行时就被加载
返回值:成功返回共享库的句柄,失败返回NULL
dlsym:查找函数
void *dlsym(void *handle,const char *symbol);
handle:共享库的句柄,dlopen的返回值
symbol:函数名
返回值:成功返回函数指针,失败返回NULL
dlclose:关闭共享库
int dlclose(void *handle);
handle:共享库的句柄,dlopen的返回值
返回值:0关闭成功,非零失败
dlerror:查看错误
char *dlerror(void)
当dlopen、dlsym、dlclose执行出错,再调用此函数获取错误信息
gcc test.c -ldl
l表示调用库
dl表示动态库
辅助工具
nm
查看目标文件、静态库、共享库、可执行文件中的符号列表
strip
删除目标文件、静态库、共享库、可执行文件中的符号列表
objdump
显示二进制模块的汇编信息
内存管理
环境变量
什么是环境变量
是程序了解操作系统配置的一个重要方法
操作系统通过环境变量来告诉程序的资源放在什么位置
每个程序执行后操作系统就给它一张环境变量表,每个程序一张
全局变量 extern char** environ
也可以通过main函数参数来获取
int main(int argc,char** argv,char** env)
操作环境变量的函数:
stdlib.h
name=value
char *getenv(const char *name);
通过环境变量名获取环境变量值
int putenv(char *string);
以name=value来设置环境变量,如果环境变量已经存在则覆盖,不存在则添加
int setenv(const char *name,const char *value,int overwrite);(返回0成功,非0失败)
以name,value方式来设置环境变量
name:环境变量名
value:环境变量值
overwrite:如果环境变量已经存在,为0则不改变,不为0则改变
int unsetenv(const char *name)
删除环境变量
int clearenv(void);
清空环境变量表
错误处理
通过函数返回值表示错误
合法或不合法
数组的查找
计算文件的大小
int file_size(const char* path);
调用ftell函数获取文件位置指针
设置文件位置指针到文件末尾
NULL或其他地址
malloc、fopen、dlopen
实现mem_cpy功能
void *mem_cpy(void *dest, const void *src, size_t n);
dest与src可能会有交集
成功返回0,失败返回-1 bool
bool top_stack(Stack* stack,TYPE* top);
dlclose
实现求余函数
int mod(int num1,int num2,int* retp);
永远成功
printf
通过全局变量来反映错误
errno.h
erron
strerror(erron)
perror(fopen)
重定义
#define TYPE int*
typedef int* TYPE;重命名
TYPE p1,p2,p3
内存管理
用户层
STL智能指针/自动分配/分支释放
C++ new/delete
C malloc/calloc/realloc/free
posix sbrk/brk 操作系统提供的内存管理方式
Linux mmap/munmap
内核层
kernel
kmalloc/vmalloc
Driver
get_free_page
DDR4
进程的映像
存储在磁盘中的可执行文件叫程序
把程序加载到内存中并执行,叫进程,进程可以看作是可执行程序的一个实例
一个程序可以有很多个实例,每个进程都有一个唯一的编号
getpid();
进程在内存中的分布情况叫进程映像,从低到高依次排列情况
代码段
可执行文件会被加载到此处
只读段
字面值、常量
全局段
初始化过的全局变量、静态变量
bss段
未初始化的全局变量、静态变量
堆
new/delete/malloc/sbrk
栈
局部变量、块变量、函数的返回值
命令行/环境变量 (最高)
命令行执行程序时附加的参数,环境变量表
/proc/pid/maps 可以查看内存的分布情况
size a.out 可以查看代码段、全局段、bss段的大小
虚拟内存
每个进程都有独立的4G(320S)的虚拟地址空间
0x00000000
0xffffffff
应用程序中用到的都是虚拟内存,永远无法访问到实际的物理内存
虚拟内存不能直接使用,需要与物理内存建立映射关系才能使用,使用没有建立映射关系的虚拟内存将发生段错误
虚拟内存与物理内存的映射是由操作系统动态维护,由操作系统统一内存的管理能提高程序和系统的安全性,还可以使用更多的内存,甚至比物理内存更大的内存。
物理内存不能直接访问,是通过系统调用进入到内核层,然后再进行间接交换
虚拟地址的0~3G用户使用,3~4G内核使用
8bit=1byte
在使用没有权限的内存时会发生段错误,使用没有映射过的内存会发生段错误
每个进程对应一个虚拟地址空间,两个进程之间进行地址交换是没有意义的
malloc背后有一个双向链表在维护它的内存管理,首次向malloc申请内存时,malloc会向操作系统申请进行内存映射(首次映射33页,1页默认4096byte)。之后的内存分配就从这33页中进行,当33页用完后,操作系统会再次映射33页
当使用malloc进行内存管理时,不要破坏维护信息
可能会影响下次内存的分配和之前内存的释放
内存的映射是以页(4096byte)为单位,一页内存的字节数可以通过getpagesize()获取
Linux内存映射函数
mmap
void *mmap(void *addr,size_t length,int prot,int flags,int fd,off_t offset)
addr
想与物理内存映射的虚拟地址,如果是NULL可以让操作系统自动选择
length
映射的字节数,以页为单位
prot
映射的权限,读写执行
PROT_EXEC
PROT_READ
PROT_WRITE
PROT_NONE
flags
MAP_SHARED 映射文件
MAP_PRIVATE 数据只写入缓冲区,不更新到文件
MAP_ANON 只映射内存
fd
映射文件时,文件描述符,如果不映射文件,写0
offset
映射文件时的偏移值,如果不映射文件,写0
返回值
映射后的内存的地址
munmap
int munmap(void *addr,size_t length);
addr
要取消映射的地址
length
字节数
返回值
成功返回0,失败返回非零
POSIX的内存管理函数
sbrk和brk维护一个内存末尾指针
sbrk
void *sbrk(intptr_t increment);
increment 把内存的末尾指针移动increment个字节
返回值:上次调用sbrk/brk的内存末尾指针
brk
int brk(void *addr);
把内存末尾指针设置为addr
返回值0表示成功,非零表示失败