Linux--变量与虚拟内存

时间:2022-05-31 23:11:51

定义一个变量:存储类型  数据类型  变量名

存储类型(变量存储的位置):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));