Linux下C语言编程快速提炼====基础篇
提 纲:
- 数据类型,表达式:
- 数据类型长度
- 数据范围推算
- 隐式类关系转换
- 短路计算
- 数组与串:
- 地址
- 数组(元素个数一定是常量)
- 字符串与字符数组区别(结果标记)
- 函数与栈帧:
- mov,push,pop,call,ret,leave...
- 反汇编工具 Objdump 使用方法
- 使用汇编指令分析栈帧图
- 函数参数与压栈顺序
- 值的返回(eax)
- 链接:
- c程序产生过程
- ELF文件格式
- c语言程序内存分布图
- 全局与局部变量
- 链接与重定位
- 符号解析规则
- 动态库
- 预处理:
- #include机制
- 带参数的宏
- 内联函数(inline)
- 条件编译
- makefile:
- 指针:
- 语法
- 指针的指针
- 指针做参数,指针做返回值
- 指针控制移动和间接引用
- void *与null
- 数组指针与行指针
- 指针的数组
- 接口设计规范
- 文件系统(部分待续)
******************************************************************************************************************************************************************************************************
###############################表达式#################################
####数据类型####
内置类型:int short chart double ...
构造类型:结构体 指针 数组 枚举 函数 ...
空类型:void
int short long 三种整型
4byte 2byte {32bit 4byte;64bit 4byte)
*CPU 指令个数 寄存器指令个数 ————>体系结构
intel amd -->i386 体系结构地址总线:32位
访问的地址总量:2*2...(32),4G.
在32位上8个字节用long long(long double 16字节)
c89中用long long? gcc a.c -o app -sd=c89;
运算符:size of()字节数求取;
typeof()求变量类型的名字;
####整型变量的范围####
int-->32bits
在内存中两种稳态:二进制表示
编码方式:补码
0有唯一编码,计算速度快
32位 第一位;符号位(1负0正);
范围-2(8n-1)~~2(8n-1)-1;n为字节数,量级int 10(9),short 10(5);
如果超出范围就溢出了(overfloat);
####浮点数####
float型,作为协处理器集成于cpu中;用移码表示,常用于科学图形计算当中,有符号位,后面31位中前M位为数值,后N位为精度;
浮点计算速度慢于整数
中央处理器『协处理器,中央处理单元 内部管理单元(memory management unit)(虚拟地址 物理地址)』之所以在主板上消失了是因为都集成于CPU中了。计算机中处
####char 字符型数据####
占用一个字节,存储字符对应的ASCII码;
####表达式####
除法永远向下去整
ALU(agrithal logical uint)算数逻辑单元 只做加法和移位运算
编程中注意速度避免用除法 乘法可以
####逻辑运算符####
支持短路运算(short circuit)
例子:char *who;
(strcmp(who,"root")!=0)&&(printf("you are not root"));
等同于 if(strcmp(who,"root")!=0){printf("you are not root")};
####隐式类型转换####
自动统一到下一个类型进行运算 小类型转换成大类型 保持精度
char-->short-->int -->unsigned int -->long-->double<--float
##########################数组与字符串################################
####数组####
变长数组 malloc int a[n];与编译器相关其采用默认的N值进行分配 不显示错误;
例子:int a[10]; a[10]不行,结果不可预测,造成数组越界了;
%d: 输出转换成十进制有符号整型
%u:无符号整型
%o,%x:用来查看 不用有符号
%s:字符串,%c字符
%p:专门用来打印地址,pointor
####存储模式####
小端法(little-endian)i386体系:高位存高字节,低位存低字节。
在编程时用虚拟地址,不是物理地址,内存不够时可以用外存。缺页异常。
访问速度:缓存50ns,内存100ns,磁盘100us,移位运算1ns,/,%10ns。
局部性原理:数据最好连续性访问。
解释操作系统:分时复用。即不同程序之间相互一直切换。
虚拟的经过MMU进行映射(内核提供算法)物理的,disc与内存进行交换,硬盘中有用的换取内存中无用的(换页机制,即缺页异常)。
佐证:访问占用内存很大的时候,不断用页面切换到磁盘中暂存即磁盘与内存的交互。
《微机原理》《数据结构》《深入理解计算机系统》《算法导论》
####串####
字符串:特殊的字符数组;多了一个结束标记“\0”,字符串首地址开始到“\0”之间的内容。例如 五个字符的串是六个字节。
字符串的初始化:char buf[1024]="hello";前5个是hello第六个是\0,以后的也都是“\0”。char buf[5]="hello";是错误的 六个字节。
int a[10];
sizeof(a),求取字节数为40。
char buf[]="hello";
sizeof(buf),为6.
sizeof(&buf[0]),地址是32位,即4个字节。
常用函数:输入一个串 char buf[1024];
scanf("%s",buf);\\输入一个串到buf中
gets(buf);
puts(buf);\\打印串
strlen 求字符串长度;strcmp 字符串比较;strcpy 字符串复制;strcat 字符串连接;
scanf函数对空格非常敏感(空格与回车等价),这时只能用gets()函数。
gets()函数没有限制输入的字符,只传输了首地址,会导致stack smash(栈崩溃)。
#############################函数与栈帧###############################
####函数 (子程序)####
返回值 函数名 (参数列表)函数的声明的话可以只写参数的类型
形参和实参。
函数名代表底层中代表函数的入口地址(编译器,入口地址,跳转),函数的第一层指令的地址。
!!!objdump 反汇编(a.c-->app(二进制可执行程序)--》汇编中的汇编语言)把一个二进制程序反成汇编语言。
-d 反汇编。
-S 列出源(通过反汇编方法了解C)。
-g 为程序生成一个可供指示的符号表。
app>out 就可以用vi编译器查找Out 中的代码。
汇编中没有数据类型的概念,主要有寄存器和内存。
单:指令 操作数;
双:指令 操作数1,操作数2;
寄存器:eax,b c d:通用寄存器,四个字节的存储空间,esi,sdi:下标寄存器,数组的下标,不能存负数。esp(栈顶地址),ebp(栈底地址):只能存地址,即指
针寄存器。eip 保存下一条指令地址。跳转都是eip相关的。
栈,先进后出。
####指令####
push %ebp :寄存器中的值压到栈里边去,esp 指向哪里就把值存到哪里。1,将esp向下移动,2,将ebp值保存到esp指向的空间。
pop %ebp :出栈,1,取出栈顶的值,将esp向上移动。
call 地址 :跳转指令,调用函数(跳到函数入口出执行) push %eip
return :返回指令,取出返回地址 pop %eip 还原到eip中
mov:(双操作指令) (数,寄存器)(寄存器,寄存器)((寄存器),寄存器(找对应的内存单元的值放到第二个寄存器中));
mov -8(%esp),%eax(%esp的地址减8,把对应内存中的值放入eax中)。
leave:1,mov %ebp,%esp,2,pop %esp。
!!!gcc main.c -o app -g 表示出c。
objdump -dS app>out
vi out
返回值都是通过eax寄存器返回的。
参数的压栈顺序是:从右向左依次入栈。
栈从上向下生长,栈顶esp栈底ebp 上面是栈底,从上向下生长。
!!!GDB
gdb list+行号 刻出代码
break+行号 设置断点
print+变量名 打印出值
next 跳转函数
step 进入函数里边
si 分条指令进行执行
disassemble 反汇编当前函数 +函数名或地址 反汇编指定的
inforegisters 显示所有寄存器当前值
start 从程序命令一步一步的执行
quit 退出
echo 打印
函数返回值:没有RETURN 就得返回上一个程序main的返回值。
段错误解释:返回值是eax 但是不能超过四个字节。
局部变量的产生:移动esp产生值,是随机的,上一个栈帧残留的。
全局变量和局部变量:1,作用域上有差别;2,生命期不同,全局变量一直存在,局部变量调用后就没有了,前者一直存在,后者调用后就没有了;3,全局变
量存在bss data段,局部变量存储在栈帧上,即存储的位置不同。
###########################C程序内存布局##############################
####ELF文件格式内部结构(磁盘中的布局图)####
ELF header:查看汇总信息头,保存可执行文件的执行信息
段 .text:代码段,存储二进制字节码
.data:数据段, 存储全局变量(局部变量放在栈帧里)
rodata:只读数据段, 存储字符串常量 “ ”。
bss:块缓冲段,存储未初始化的全局变量,beiler save space 节省空间。
用于链接 .rel.data:在模块链接数数据的重新定位。
.rel.text:在模块链接时代码的重新定位。
.symblol:文件内的名字 保存全局符号。
.debug:给gdb用调试信息的。
ELF(tail)
!!!strip -s app 拔去用户无用的段
objcpy 把ELF中除了四个段以外的全部内容删掉。
从磁盘中拷贝到内存的过程叫做加载(load)。
!!!ldd app 查看库
readelf -s 读取ELF文件中各个段的表象。
##################################链接################################
####一个源文件生成最后的可执行程序####
a.c-->1 a.i-->2 a.s-->3 a.o-->4 app(二进制可编程程序)
1,预处理(比如注释,.c文件转换成有用的源代码,加快转换速度);
2,编译(翻译成汇编语言代码);
3,汇编 目标文件 .o;
4,链接 合并的过程,将多个.o文件合成一个可执行程序。
!!!-E 只执行预处理
-S 只进行编译
-c 只进行汇编
虚拟地址=段首地址(由链接脚本决定)+逻辑地址(从0 开始,即偏移)。
偏移不是真值,引用函数的时候需要重新填写,这个操作成为重定位。WHY???
每一个.c文件对应一个.o,多个.c对应多个.o,(函数的相对位置发生了变化),程序链接:函数和全局变量位置发生变化,call调用时地址可能无效,改成链接
后的地址再进行重新定位。
多文件编译:两个或者多个.o文件合并的过程为链接。
!!!file app.o 查看文件
readelf -S app.o 读取各个表象
objdump -xdS -x 是列出重定位信息
链接与加载的信息包含INIT 函数(程序入口 call main函数),在合并.o文件过程中加进来,.text代码段增加许多。
####符号解析规则####
int a;(声明)int a=100;(定义 初始化了的a)
1,不允许有多个定义,不允许同一个符号定义多次;(定义成什么值无所谓就是不能重新定义);
2,有一定义多个声明,选择使用符号时使定义的符号;(允许一个符号定义,多次声明可以);
3,多个声明则选择其中一个(对于一个变量多次声明,只有声明没有定义是允许的,因为其中一个自动升为定义,但函数不允许,其中必须要定义);
内部局部变量和外部全局变量不冲突,因为作用域不同;函数内部引用全局变量是可以的。
double x;
void f()
{
x=0;
}
#include<stdio.h>
int x = 15132;
int y = 15134;
void f();
int main(int argc,char *argv[])
{
f();
printf("x=0x%x y=0x%x\n",x,y);
}
输出结果是 0 0 ,可以用重定位的知识解释,反汇编查看可以知道地址可以回填但是指令并没有改变,所以x过了4个字节后把y的4个字节都覆盖了,都是零了,结果就是 0 0。
!!!ldd app 库名字显示
libc.so.6 C库 so (share object)
ld-linux.so.2 动态链接器 实现链接
####动态库(共享库)DLL dynamic linking library####
库中包含所有函数的实现(二进制字节码的形式存在);二进制可执行文件(.elf)
so中有几个ID要存储,在.elf中多了一张符号表,存在与代码段和数据段之间,指定一个ID就可以查询对应的函数入口地址。即动态库中ID与入口地址是一一对应
的,.so文件拥有的。
标准C库中有900K。
动态库的特点:
1,动态链接(发生在程序运行的时候)
编译时并不合在一起而是作标记,添加信息。运行时直接连接内存中有的库。如果库出现在内存中,直接加载应用不用加载库了,如果库不存在,则先加载库,
再加载程序,即动态库的加载一定在程序加载之前。当程序运行时库和程序之间不断进行跳转。
2,共享(如果在程序运行之前库已经存在,在内存中被多个程序共享使用,节省了空间);
问题:动态库不是一个使用,在运行之前可能有人已经使用了,每次用时库加载的地址可能不同,call后面的地址代码要修改,即肯定不行,这个时候需要子程
序链接表(procedure linkage table)
不管加载到什么位置,call都跳转到一个相应的位置上才可以,缺点是慢。
函数地址变化,但是对应的PLT表地址固定(puts@plt--puts 函数在 plt中的地址)
运行时 GOT表,GOT【2】动态入口地址;GOT【1】动态链接器的描述信息;GOT【0】第0个表项。寻早要用的函数给地址,此过程为延迟绑定(懒惰算
法),PLT表项中16个字节清空,放入函数地址,下一次用的时候直接跳转到表项中获取函数地址。
!!! gcc main.c -c (汇编 生成.o文件)
gcc main.o -o app
ldd app (产生相关库信息自动加载的)
objdump -dS app>out (反汇编)
objdump -dS -j .plt app>out -j 只反汇编一个段
objdump -dS -j .got.plt app>>out 追加
编译vi.
printf 格式化打印;puts()不用格式化打印;
!!!history
ps ax 正在运行的
动态库用 .so .so.6 6是版本号
####制作动态库####
-Share 可以动态链接的 -fPIC 生成未使用过的代码,生成id首地址对应的表。
1,gcc add.c -o add.so -Shared -fPIC
readelf -dS add.so 段查询
自己编写的程序就可以链接动态库
2,gcc main.c ./add.so -o app -->包含动态库信息的app
objdump -dS app
更新即版本不同(mv 指令实现)动态库更新方便 只要接口不变 只需要应用程序暂停再重新链接动态库使用。
自主学习的能力很中要,要有基本概念,例如,现代化工业软件开发,如见开发和发布的模式接口设计模式等等。add.so(产品),add.h(放在main.c中声明)
add.h 帮助文档提供给用户。.so文件也要打包给用户。
##############################预处理##################################
####linux下可执行程序都是ELF格式的####
1,文件包含,2,宏定义,3条件编译。对人有用但是对及其没用!
1,文件包含;(机制是复制)
#include<stdio.h> 间括号 是系统指定的目录中搜索文件 /usr/include
#include"stdio.h" 双引号 当前目录下搜索需要的头文件
需要注意的是:头文件中可以有:1,函数的声明;2,可以加全局变量的声明;3,宏定义可以有;4,新的类型定义;5,用include包含其他文件;不能有的
是:变量的定义和函数的定义。
隐式声明:要是调用一个函数之前应声明,但是gcc编译器自动判断函数类型。
gcc中指定文件包含的路径,a.c中head.h不再当前目录下而在include目录下,则如下命令 gcc a.c -c -Iinclude
2,宏定义,让程序更简洁,代码修改更方便,易于维护,易于理解。
定义一个宏,#define 标识符 字符串 #define G 9.8
注意:宏的定义不作数据检查,有效范围是限于文件内部,提前取消宏的话 # undef 宏名
定义一个全局的宏:放入头文件中,每个.c include头文件就可以了
带参数的宏(很像函数 但有区别),#define 宏(参数表) 字符串
宏的参数没有类型(因为是在预处理时发生的) 宏定义多行时用续行符‘\’
宏的副作用 #define test(a,b) (a)*(b) 因为 test(a+b,a-b)时 可能a+b*a-b 运算顺序发生了变化。前面的是正确写法。
宏与函数的区别:1,宏是编译时的概念,函数是运行时的概念;2宏在编译时发生运行速度比较快,函数在运行时发生速度比较慢(压栈,跳转等)3,宏不会
对参数类型进行检查,函数有严格的类型检查;4,宏不会分配存储单元,函数自己分配存储单元(函数会产生栈帧,宏不会);5,宏会使代码体积变大,而函数不会。
6,宏不可以调用本身,函数可以。
inline关键字 将函数展开,不进行压栈跳转等,在过程中直接将函数展开。
内联:不跳转,压栈等类似于宏,是更好的宏。把代码给展开不进行跳转,内联函数有宏的特性,又弥补了宏的特点,即不进行检查,在优化后进行展开。
编译器可以自己指定内联的函数,inline 可以手动指定内联函数。
3,条件编译
#ifdef 标识符 程序段1(如果有标识符,则。。。)
#else 程序段2 (否则。。。)
#endif
有什么用??? --调试输出(调试开关)只要一个地方作改动,整体都变化。调试开关是一个技巧。
gcc DEBUG.c -o app3 -g -DDEBUG //由编译器在我们的源代码中定义某个宏
#ifdef DEBUG
#define DEBUG_PRINT1(arg0,arg1)printf(arg0,arg1);
#else
#define DEBUG_PRINT2(arg0,arg1)printf(arg0,arg1);
#endif
int _strlen(char str[])
{
int i;
for(i=0;str[i]!='0';i++)
DEBUG_PRINT1("%c",str[i]);
return i;
}
################################Makefile################################
####Linux 下的脚本,对代码的自动识别和编译####
1,自动编译(许多.c文件)输入一个命令就可以自动编译
2,选择编译(实现筛选,自动编译)
target(产品):prerequest(依赖软件) 要生成产品就要有很多依赖文件。
commond 命令(怎么样依赖文件来生成产品)
依赖的文件可以修改但是一定要存在;
命令必须以制表位开头;
产品 目标 命令 --》规则,Makefile中就是一个接着一个的规则。
依赖文件要新于目标文件,用较新的文件生成更新的目标文件。体现了选择性编译,看依赖文件是否新于目标文件时间上能看出,就能生成新的目标。
####多条规则的Makefile####
如果文件和目标文件没有关系则没有用,即孤岛规则。默认规则目标为最终目标。
执行Makefile的最终目的就是执行最终目标。
make 目标名 手动指定最终目标。
规则中只有目标是必须的,命令与依赖可以省去。
例子:(release 发布程序)
release:a.c
gcc a.c -o release (strip -s release)用一个规则可以执行多个命令。
debug:a.c
gcc a.c -o debug -g DDEBUG
clean: -->命令一定被执行到
rm -f *.o
rm -f app
echo "job done"
clean 是伪目标,不依赖于任何文件,只要能被执行到一定执行命令,用来快速删除编译过程中产生的中间产品。
all:app app-r all最终生成的可以没有命令。
快速生成依赖工具 all:*.c
gcc *.c -o app
make debug 调试程序;make release 发布程序。
################################指针###################################
类型名 *变量名
int *p = &a;
p 直接引用 ;引用p的值 *p 间接引用 ; 取指针变量指向的变量
指针变量也是变量。
int *p,q;//同类型的变量可以写在一行
int a,b; p=&a;
q=&b;//整型变量不用赋&b的值,会出现警告。
typedef 已有类型 新类型(别名 _t是标准);//把程序中已知类型定义成自己的新类型
typedef int my-int_t;
typedef int * int p_t; int p_t p,q;//p,q都是指针类型,此处体现了与宏定义的不同,要是宏的话,q就不是指针类型了。
直接引用的不用管p中的内容新的内容直接把它覆盖了。
间接引用 *p=10;将10付给p所指向的空间,可能引起段错误,用间接引用的要清楚p所指向的空间是否有效,否则引起段错误。
数组是首地址,指针也是地址,则*p《=》p[0]
1, char *p="hello";//p指向的空间是只读数据段
*p=‘h’;//不可以,因为只读数据段不能修改,出现段错误
2, char p[]="hello";
*p='H';//结果是首字母大写,*p<=>p[0](都是首地址),hello串从rodata段复制到了栈中局部数组中,p[]为局部数组初始成“hello\0”,等价于char
p[6];strcpy(p,"hello");
3, char *p;//空有一个指针P。
strcpy(p,"hello"); //p指向的空间不一定是有效的可以访问的空间,所以“hello”不能被复制到p指向的空间当中。
修改:char buf[10];
char *p;p=buf;
内部的char strcpy(char *p, char *q){
while(*q!='\0'){
*p=*q;p++;q++;
}*p='\0';return p;
}
间接引用指针变量时候必须保证变量指向的空间有效即可以访问。
####指针的指针####
int **p;//p是个指针变量,保存了指针变量的地址。
int **p,*q;
int a;a=100;
q=&a;p=&q;//保存了指针变量的地址,两个*就是极限了
int **p,*q;
int a;a=100;
p=&q;*p=&a;//*p=q,q=&a,->*p=&a;必须保证p指向的空间有效,指针变量也有地址,即加上p=&q.等价于a的首地址放入到q中,q=&a。
####指针变量作函数的参数####
void swap(int a,int b)
{
int t;
t=a;a=b;b=t;
}
int main(void)
{
int a=1,b=2;
swap(a,b);
return 0;
}
//结果还是1,2.没有实现交换的功能,副本改变了,但是本体并没有改变。
改正:swap(int *a,int *b);t=*a;*a=*b;*b=t;swap(&a,&b);
指针指向变量类型,意义在于,1,控制间接引用;2,控制指针移动
隐式类型转换:强制--》主要用在指针上。强制类型转换,(新类型)表达式;int *p;(char *)p;
指针做到了数据类型还原成字节,就像汇编一样。编写C,低于9K是不行的。
void memcpy(void *d, void *s,int n)//转换n个字节从s到d,void *表示泛型指针,即任意类型指针。void *p;*p;不能这么用,p指向的类型不定,所以取的字节数不确定。
{
char *s1, *s2;
int i;
s1=(char *)d;
s2=(char *)s;
for(i=0;i<n;i++)
{
*s1 = *s2;
s1++;s2++;
}
}
编写一个程序判断大小端:
int test_endian(void)
{
int a=0x12345678;
return 0x78==*((char *)&a);
}//返回1小端,0则大端。(char *)一个字节,78;(short *),两个字节,5678;
####指针指向指针变量类型典型用法:控制指针移动####
int a;
int p=&a;
char *p="hello";
p++;//p跳过的不是一个字节,而是p+i*sizeof(type) 个字节,p++跳过的是p指向的变量的字节数。
####NULL:空指针(相当于宏) #define NULL (void *)0####
0号地址单元,永远不能访问。清零作用,char *p=NULL;预处理之后等价于,char *p=(void *)0;char *p=NULL;*P=‘A’;会出现段错误,因为在间接引用之前没
有保证p所指向的空间是有效的
三种段错误:1,char *p="hello";*p='h';2,char *p;strcpy(p,"hello");3,int **p;int a;*p=&a;
####指针作参数####
间接引用时可以对主函数影响,例如swap()函数;直接引用作参数时对主函数没有作用;void fan(char *p){p=NULL}int main(void){char *p=0x200;fac(p);return 0;}
结果还是0x200。
####指针作返回值:不能返回局部变量的首地址。int *f(void){int a=100;return &a;}不行,&a是无效的,称之为野指针。
####指针指向变量的类型:1,控制指针移动;2,控制间接引用。
####泛型指针:1,不能间接引用;2,不能进行移动;3,NULL 4字节;
####typedef与宏的区别:加一个引号。
C中三种0常量:1,0:4bytes,2,'\0':1byte,3,NULL,4bytes;
####数组和指针的关系
int a[10];int *p;p=a;/p=&a[0];
数组名实际上和地址是等效的。
1.for(i=0;i<10;i++)
a[i]=i;
2.for(i=0;i<10;i++)
*p++=i;
3.for(i=0;i<10;i++)
p[i]=i;
4.for(i=0;i<10;i++)
*(a+i)=i;
汇编中,a[2]《=》*(a+2) 编译速度提高了但是没有必要。
*(a+i)=a[i]--*a=a[0];
(a+i)=&a[i]--a=&a[0];
调用函数时把数组的首地址作为参数传递进来;
实参是地址,即可以用指针代替;
不同之处:数组名是地址常量,指针是变量;
int a[10];
int *p;
p=a;(a=p是错误的)
*p++;(*a++是错误的)
####数组的指针#### (指向一个数组)
int (*p)[5];//定义一个指向数组的指针,括号是不能省的,p是变量名。
int a[5];p=&a;(p=a是不行的),数组整体看成一个对象给p,&符号不是说取数组地址,是告诉编译器将a当作整体来看待,并不会产生额外地址,&a是数组a的首
地址。
void fac(int a[])-->等价于 int *a{return a[0];} return sizeof(a);恒为4,调用函数时把数组的首地址作为参数传递进来。
int main(void){int a[]={1,2,3,4,5};f(a);} sizeof(a)--20bytes.因为实参为地址,所以可以用指针代替。void(char *a)
void bubble_sort(int a[],int n) 因为内部求不出元素的个数,需要n来定义元素的个数。体现了接口设计的思想(科学简洁,扩展性强)。
如何设计接口,要科学,与别人的程序对接,要掌握软件开发的基本流程,模块化的设计思想。
####指针的使用在c中很灵活####
int a[]={1,2,3,4,5}
printf("%d,%d",*(a+1),*(((int *)(&a+1))-1)); &a+1;//跳转的是整个数组,强制转换成int *指针跳转4bytes,-1之后,跳转到 5上。!!!!!!!!!
####行指针####
指向一行的指针。
1,已知 int a[10];-->int * ; int a[2][3];-->int ** 不成立。
2,已知 T a[]; --> T *;
3,已知二维数组可以看成是一维的,每个元素是一行,int a[2][3];-->行型 a[2]; 行 *p;--》数组指针。a [2][3];<--> int (*p)[3];*p是行指针,3是3个元素,a+1 是第一
行。
####接口设计####
函数中要返回副本,不要把原文件给别人,一般是返回值。
传出参数:1,必须是指针;2,为了传出返回值而存在的;
例子:strcpy(s1,s2);s1就是传出参数。
typedef struct{int max;int pos;} res_t;
void get_max(int a[],int n,res_t *r);//接口的设计,结构体中可以修改,但是接口不变,做到了兼容,每次函数升级,接口并不用变。
int get_max(int a[],int n,int *pos)
{
int max, i, p=0;
max=a[0];
for(i=0;i<n;i++)
{
if(a[i]>max)
max=a[i];p=i;
*pos=p;
return max;
}
}
int main(void)
{
int a[]={1,2,3,4,5};
int m, p;
m=get_max(a, 5, &p);
printf("max: %d at: %d",m,p);
return 0;
}
pos:是传出参数,value-out.
传入参数:函数内部作数值用,不作改变,与传出参数对应,例如:strlen(char *s);char *s 就是传入参数,调用之前有用,函数的过程中不改变
传入传出参数:冒泡函数 void bubble_sort(int *n,int n);int *n是传入传出参数。
接口要有容错能力,容错方案解决:
void get_max(int a[],int n,res_t *r)
{
static res_t bakap;
if(r=NULL) r=&bakap;
return r;
}
正常缓冲区的话返回自己的,如果是null 提供缓冲区bakap供返回
char * (返回的是字符型的指针) get_common(char *s1,char *s2,char *out,int size) s1,s2是传入参数,out是传出参数,可能是有效或者无效的空间,考虑到安全
性,要有size ,这是完整的接口设计。
操作系统--》管理者,底层实现者。linux 0成功;-1失败。
系统调用,是一个函数,是操作系统的一部分,内核函数,系统调用特权级。自己用的是用户级。API 用户编程接口,上层是应用层,下层是系统层。
####malloc#### 动态分配内存块(程序运行的进程中)
程序编写时空间大小不确定,要用动态分配函数malloc,动态分配内存。
size_t 无符号长整型
void *malloc(size_t size) size 动态分配的字节个数
void *动态分配首地址作为返回值(从首地址开始向后的size个字节都是可以使用的),成功的话返回,错误的话就返回NULL(ENOMEM 没有内存)
malloc函数系统调由sbark负责向系统申请内存。
int main(void)
{
int n, i;
int *a;
scanf("%d",&n);
a=(int *)malloc(sizeof(int)*n);//动态申请4n个字节。
for(i=0;i<n;i++)
scanf("%d",&a[i]);
for(i=0;i<n;i++)
printf("%d\n",a[i]);
free(a);
}
void free(void *ptr)//释放内存的首地址,没有返回值。
free(a);//有申请就要有释放。
系统调用浪费时间,要尽量避免,sbark向系统内核作系统调用。
堆(heap)中存malloc动态申请的内存,堆是只升不落的,malloc机制是只申请不退还。
typedef struct{int size:29,pom:3(位域,加快内存块的权限)}mem_t 是管理结构
29+3 32位,前29位内存块的大小,后3位权限(前两位是读写可执行权限,后一位判断内存块1忙0闲)????????????
#############################文件操作#################################
FILE *fopen(const char *path,const char *mode)
//结构体,用来引用打开的文件。path 路径。mode 权限, 文件模式。
w:只写打开文件,文件如果存在的话将自动截到0,即源文件扔掉,自动创建。
w+:读写,如果不存在文件自动创建,否则自动截短到0,原文件扔掉。
r+:更倾向于读,没有w+的功能,正常读写。
悬挂:a:写入的内容自动追加到结尾,a+:写文件尾,没有的话自动创建,存在的话写在结尾。
返回结构体文件指针,如果失败返回NULL。
读方式打开目录可以,写不可以。
#include<stdio.h>
int main(void)
{
FILE *fp;
fp=fopen("test","r");
if(fp==NULL)
perror("error occurs ...");//参数自己定义的字符串,把错误的原因接在串后边
}
!!!ls -l test
chmod u-x text
三种错误:1,没有文件;2,权限不够;3,打开目录(只写)。
int fclose(FILE *fp) //要关闭的文件的指针,用来关闭文件成功返回0,失败返回-1.
如果文件不关闭,长时间的话就打不开了。
####以字节为单位的读####
int fgetc(FILE *stream) 文件结构类型(引用打开文件) 读一个字节作返回值返回(拓展成 int)。
EOF :文件结束标记,END OF FILE
char ch;
while(1){
ch=fgetc(fp);
if(ch==EOF) break;
fputc(int c,FILE *stream) //输出一个字节,int中前三个丢掉,最后一个保留。
####行为单位的读取####
char *fgets(char *s,(用来保存读取的内容)int size,FILE *steam) size 为最多读取的size-1个字节。
从F中读取到s中,size 缓冲区的大小,体现了接口设计的安全性。
vi编译器会自动检测有没有换行,读到换行‘/’ 时结束。
int fputs(const *s,FILE *steam)
成功返回0,失败返回-1.
s指向的空间必须是串,以‘\0’结束。比较长的文件多次读完。
块I/O,fread fwrite
size_t(读取的对象个数) fread(void *str,size_t size,(对象字节数),size_t n,(读取n个) FILE *steam)
size_t(返回实际输出的对象个数)fwrite(char *str,(待输出的内容)size_t size,size_t n,FILE *steam(向哪个文件输出))
int main(void)
{
FILE *fp,*out;
int n,i;
char buf[5];
fp=fopen("test","r");
out=fopen("out","w");
fread(buf,sizeof(char),5,fp);
fwrite(buf,sizeof(char),5,out);
fclose(fp);
fclose(out);
return 0;
}
操作系统的特点:长期运行, 高权级;
操作系统提供给我们底层的抽象,应用层使用接口就行了,系统调用就是这个接口,利用它就可以操控底层的资源了。
系统调用操作内核数据结构,操作底层硬件资源,库函数封装了系统调用,系统调用实现库函数功能。
*****************************************************************************************************************************************************************************************************