定义一个变量:存储类型 数据类型 变量名
存储类型(变量存储的位置):auto、register、static、extern
1、auto:对于局部变量,atuo可以缺省。位置:栈
2、extern:用来声明全局变量(在当前文件被引用,在其他文件中定义);对于函数,extern可以缺省。位置:初始化的全局变量位于数据段,未初始化的位于bss段。
3、register: 用来定义频繁被使用的变量(局部、整形、字符型),位置:寄存器
4、static:同extern可以修饰变量和函数。修饰变量分为静态全局变量和函数体内的静态变量;位置:初始化的全局变量位于数据段,未初始化的位于bss段。
5、const: 保护传参数时,原数据不被修改。位置:局部常量位于栈,全局常量位于代码段。
6、volatile:
7、enum: 应对一个变量可能存在多个值,枚举类型即将变量所有可能的值(枚举值)一一列举出来。
(1)定义枚举类型:“enum 类型名{枚举值1,枚举值2,……,枚举值n}”。
(2)定义枚举变量:“enum 类型名 变量名”,或者“类型名 变量名”。
(3)枚举值是常量、只能将枚举值赋给枚举变量、枚举值可以是有意义的字符串、枚举值默认从0开始递增。
eg:
enum week{Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday};
enum week day1,day2;
day1 = Wednesday;(day1为2)
8、union: 公用体类似结构体可以定义多个成员变量,但与结构体不同的是共用体共享同一段内存空间,即在某一时刻只能存储其中的一个成员。
eg:
union fun1{
char a[10]; //10字节
double b; //8字节
}
fun1占16个字节(double为union中最大的数据类型,根据内存对齐,空间大小要为8的整数倍)
union fun2{
char a; //1字节
double b; //8字节
}
fun2占8字节
union{
int i;
char a[2];
} u;
u.a[0] = 0x39;
u.a[1] = 0x38;
u.i 的值应该为多少呢?
这里需要考虑存储模式:大端模式和小端模式。
大端模式(Big_endian):字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。
小端模式(Little_endian):字数据的高字节存储在高地址中,而字数据的低字节则存放在低地址中。
union 型数据所占的空间等于其最大的成员所占的空间。对union 型的成员的存取都是相对于该联合体基地址的偏移量为0 处开始,也就是联合体的访问不论对哪个变量的存取都是从union 的首地址位置开始。
判断大端模式:
int checkSystem( )
{
union check
{
int i;
char ch;
} c;
c.i = 1;
return (c.ch ==1);
}
9、typedef:
#include <stdio.h>
int add(int a , int b){
Return a+b;
}
void main(){
int (*fun)(int) = (int (*)(int) )add;
int r = fun(20); //正确
//int r = fun(20,30,40); //正确
printf(“%d\n”,r);
}
函数调用后,在栈中为它的参数分配空间,用实参去初始化形参。上面代码,只为第一个参数初始化,第二个参数没有初始化为随机值。
总结:
1、函数执行的时候有自己的临时栈空间。
2、函数的参数就在临时栈中。如果函数传递实参,则用来初始化临时参数变量。
3、参数传递方式:按值传递、按地址传递、按引用传递
4、函数返回:通过参数返回、通过返回值返回。
_stdcall、_cdecl、_fastcall 函数修饰属性,3种调用方式
int _attribute_((stdcall)) add(int , int); //linux
int _stdcall add(int,int); //windows
通过gcc -S查看汇编,可以看出差异。
_cdecl:所有参数从右到左依次入栈,由调用者负责把参数压入栈,最后也是由调用者负责清除栈的内容
_stdcall:所有参数从右到左依次入栈,由调用者负责把参数压入栈,最后由被调用者负责清除栈的内容
1、决定函数参数压栈顺序
2、决定函数栈清空方式
3、windows里将决定了函数的名字转换
Windows中cl编译命令、link连接命令
far、near、huge指针。far指的是16位寻址、far是32位寻址、huge是综合。
虚拟内存:
问题:
#include <stdio.h>
#include <stdlib.h>
main(){
int * a = malloc(4);
*a=9999;
printf(“%p\n”,a);
while(1);
}
#include <stdio.h>
#include <stdlib.h>
main(){
int * a = 第一个程序中开辟空间的地址;
printf(“%p\n”,a);
while(1);
}
问:会不会打印出9999?
结果:出现段错误
#include <stdio.h>
#include <stdlib.h>
main(){
int * p = malloc(0);
*p=9999;
printf(“%d\n”,*p);
}
问:会不会出现段错误
结果:打印9999
一个程序不能访问另外一个程序的地址指向的空间。
1、每个程序的开始地址都是0x080084000
2、程序中使用的地址都是逻辑地址,仅仅是个编号,用int来表示(32位机,寻址空间4G)。
3、每个程序提供了4G访问能力
4、逻辑地址与物理地址进行关联才有意义,这个过程称为内存映射
5、虚拟内存的提出,禁止用户直接访问物理地址,有助于系统的稳定。
6、以4K(16进制1000)为基本单位进行映射,称为内存页。需要分配4个字节,也会映射4K的空间。
段错误:无效访问,访问超出了该进程所能访问的空间。
非法访问:比如malloc分配的空间之外的空间可以访问,但是非法访问。
虚拟内存分配:
栈:编译器自动生成代码负责维护
堆:地址是否映射,映射的空间是否被管理
1、brk/sbrk 内存映射函数
int brk(void * end);
void *sbrk(int size);
#include <stdio.h>
#include <unistd.h>
void main(){
int *p = sbrk(4); //分配4个字节空间
*p = 8888;
printf(“%d\n”,*p);
}
#include <stdio.h>
#include <unistd.h>
void main(){
int *p = sbrk(0); //虚拟地址的首地址
*p = 8888;
printf(“%d\n”,*p);
}
结果:段错误
#include <stdio.h>
#include <unistd.h>
void main(){
int *p = sbrk(0); //虚拟地址的首地址
brk(p+1);
*p = 8888;
printf(“%d\n”,*p);
}
结果:没问题
#include <stdio.h>
#include <unistd.h>
void main(){
int *p = sbrk(0); //虚拟地址的首地址
brk(p+1);
*p = 8888;
brk(p);
*p = 999;
printf(“%d\n”,*p);
}
结果:段错误
应用:
1、sbrk分配空间
2、sbrk得到没有映射的虚拟地址
3、brk分配空间
4、brk释放空间
sbrk与brk后台系统维护一个指针,指针默认是null。
调用sbrk,判定指针是否是0,是:返回空闲首地址(空间为4K的倍数)初始化该指针,并且把指针位置+size。否:直接返回指针,并且把指针位置+size
sbrk控制起始端
brk控制末端
应用案例:
写一个程序查找1-10000之间所有的素数。并且存放到缓冲,然后打印
缓冲的实现使用sbrk/brk
对比:
new、malloc、brk/sbrk、stl、智能指针
异常处理:
int brk(void *)
void * sbrk(int)
如果成功:brk返回0,sbrk返回指针
如果失败:brk返回-1,sbrk返回(void*)-1
unix函数出错以后,修改内部变量:errno
#include <errno.h>
输出错误原因:
(1)perror #include<stdlib.h> 参数 s 所指的字符串会先打印出,后面再加上错误原因字符串
范例
#include<stdio.h>
intmain(void){
FILE*fp;
fp=fopen("/root/noexitfile","r+");
if(NULL==fp){
perror("/root/noexitfile");
}
return0;
}
运行结果
[root@localhost io]# gcc perror.c
[root@localhost io]# ./a.out
/root/noexitfile: No such file or directory
(2)printf(“%m”);
(3)首先extern int errno 然后 printf(“%s”,strerror(errno));