内存空间
如何访问一个空间
有名访问
通过定义变量,以变量名为名称进行访问。
int a; char b; struct buffer data;
变量定义在内存上,内存资源为了能让CPU访问的到,必须编址
通过名字访问时,对CPU来说,变量名只是地址的一个代号而已
无名访问
空间的本质:用一个地址进行编址,使CPU可以访问
如何保存地址的值?
数字概念:足够存储地址的大小
物理含义:和普通数字有什么区别?
C语言如何用地址来描述一个空间
一个地址描述空间,这个地址要满足的要素:
- 具备存储地址大小的容器,如32位系统和64位系统
- 地址从1个单位到下一个单位,该如何访问
- 为什么要提到单位这个概念?
- 软件最底层的单位大小是:1B
- C语言定义了不同的单位大小:int , short, char, struct xx
- 为什么要提到单位这个概念?
C语言编译器如何识别地址的二要素?
int * p1
这个占据的地址有多大其实是不确定的,如果是32位系统就是4个字节,如果是64位系统就是8个字节;
(在面试题中要小心问你这种题,一定要看清楚是32位还是64位系统,如果不告诉你那么就是不确定)
而对于p1地址如何访问呢?前面的int 不是修饰p1的大小,而是修饰p1从这个空间到另外一个空间是怎么访问的,就是一个int一个int(四个字节)
char *p2
练习
代码验证-地址二元素
空间大小和你目标系统的位数有关.
64位系统下
int main ()
{
int *p1;
char *b2;
printf("%d,%d", sizeof(p1), sizeof(b2));
return 0;
}
32位系统下
验证访问方式
#include<stdio.h>
int main ()
{
int *p1= (int *)100;
char *b2;(char *)100;
printf("%d,%d\n", sizeof(p1), sizeof(b2));
printf("%p,%p\n", p1, b2);
printf("%p,%p\n", p1+1, b2+1);
return 0;
}
自己该如何判断变量属性
定位变量名 向右看 再左看
int a1;
int a2[5];
定位到a2向右看到数组,a2升级为数组名,要回答两个问题1. 有多少个数组2.每一个元素是怎么存的。5个元素,5个int空间int * a3;
a3升级为地址,这把钥匙四个字节的操作int *a4[5];
a4向右看是一个5个元素的数组,右边没有了再向左看是*
那么*
表示的是a4里5个元素每个都存了一把钥匙,再向左int,表示访问方式一个int一个int的访问———>即我有五把钥匙,每个钥匙都是4个字节4个字节访问的int (*a5)[5]
a5向右看是括号,所以向左是*
这时候a5升级为一把钥匙,*a5
向右看是[5]是数组相当于是一串一串的访问,再看int即5个int,5个int的访问int a6[3][5]
a6和a5类似是都是五个int五个int的访问,只是a6明确的告诉我们他有三串int*a7[3][5]
a7向右看升级为数组,二维数组15个元素,每个元素都存了一把钥匙,访问方式是一个int一个int访问int*a8[3][4]
a8是一把钥匙,开门的方式是三行四列的开。
代码验证-多维空间存储
/*
* 设计一个指针,可以存储二维空间,或三维空间的首地址
* */
#include<stdio.h>
int main()
{
int a[3][4];
int *k1;
k1=a;
printf("a = %p , a+1 = %p\n",a,a+1);
printf("k1 = %p , k1+1 = %p\n",k1,k1+1);
return 0;
}
证明,a是4个int访问,k1是1个int访问
改进
int a[3][4];
int (*k1)[4];
k1=a;
printf("a = %p , a+1 = %p\n",a,a+1);
printf("k1 = %p , k1+1 = %p\n",k1,k1+1);
把k1升级为一把钥匙让他以4个int访问就可以和上面的a相同
三维
int a[5][3][4];
int (*k1)[3][4];
a是5个面,每个面三行四列的存储,而k1是一把钥匙,他不管你有几个面,他关注的是每个面的情况,
但是以要定义一个int *p1
让p1=a也不是不行,就比如你有五十平的房子,你非要用十平米十平米来度量房子也不是不行。(但是c++不行)//C语言更偏向于底层,C++更偏向于业务。
函数地址的保存
定义了一个函数,那么函数也是一个地址
-
函数是空间
- 函数名就是这个空间地址的常量值的代号
-
如何用一个变量保存这个代号吶?
-
保存这个代号,首先必须是一个地址
-
案例:printf的换名
myshow=printf这个思路
myshow("hello world!\n");
- 关键是怎么让myshow=printf呢?
- 只要明白printf访问内存的方式告诉钥匙,就可以了
- 假设printf长成
int xxx (void)
那么你的myshow要长成int (*myshow)(void)
首先你的myshow应该是一把钥匙,这把钥匙读内存的方式应该和替换的一样。xxx叫做函数的名字常量,myshow叫做函数地址的变量
-
代码——模拟计划执行表的案例
-
一周7天,每天做不同的事情
-
方法一 swich /case (缺点:1.代码长不利于维护2.代码过于死板,必须要该case里面的内容才可以,不够灵活。)
#include<stdio.h> void do_music(){ printf("play music\n"); } void do_game(){ printf("play game\n"); } void do_book(){ printf("read book\n"); } int main() { int day; printf("input day"); scanf("%d",&day); switch (day){ case 1: do_music(); break; case 2: do_game(); break; case 3: do_book(); break; } return 0; }
-
方法二 我们可以把无序的东西编程有序的东西,可以考虑用一个数组来实现
先假设定义三个箱子
int data[3]
如果我们想访问第二天那就是
data[1]
,我们拿到data[1]
过后如果他是把钥匙而且是函数我们可以直接data[1]()
进行访问,那么就完成第二天要做的事情。那么第二天里面装什么呢?
我们可以对
data[1]
赋值,因为data[1]
是可变的,我们可以吧data[1]
做一个借口让用户去执行#include<stdio.h> void do_music(){ printf("play music\n"); } void do_game(){ printf("play game\n"); } void do_book(){ printf("read book\n"); } int main () { int i; //定义一个数组空间,保存了钥匙,每把钥匙都是函数行为 void (*events[3])(void); //设置每天做到事情 events[0]=do_game;//赋值函数的首地址 events[1]=do_book; events[2]=do_music; //循环执行每天的事情 for(i=0;i<3;i++) { events[i](); }
-
-
-
空间属性的概述
- 引例: 空间可以随意访问吗?
#include<stdio.h>
int main()
{
1. char *s ="Aallo";
2. char s[]="Aallo";
s[0]='H';
printf("%s\n",s);
}
在执行代码1.时,直接终端直接退出了不显示任何东西
在执行代码2.时,显示Hello
-
- 为了便于管理,空间进行分段
内存结构
段 属性
4G - 3G OS kernel 不可读不可写,一旦操作被操作系统终止
--向下--(从大到小)----------------------------------------------------
stack段 所有的函数局部变量都在这里 可读可写 存临时区域
每个子函数都拥有一个独立的栈空间
每个子函数的栈空间有大小限制,一旦超过这个限制,栈溢出
堆段 C语言编译器不维护,由程序员自己来维护
空间没有限制 可读可写
通过malloc申请空间
通过free释放空间,释放了空间,该空间仍然可以访问
如果不释放,导致系统内存泄露 变慢
数据段 不依赖于函数的调用而诞生
生命周期是从程序运行开始,到程序运行结束
全局变量、静态变量
只读数据段 双引号 常量数字
代码段 只读
---------------------------------------------
操作系统保护区 不可读不可写
0x0---------------------------------------------
刚才1.里的代码s存了只读段里的钥匙,而我们写写只读段里的东西是不被允许的
-
不同的变量,默认定义在不同的段内。
-
- 函数代码段:代码段 .text段 只能读
- 字符串、常量值:只读数据段 .rodata段
- 全局的变量:数据段 .data段
- 堆段:malloc申请的,必须通过free释放
- 栈段:函数的局部变量,函数返回后,出栈释放
- static的变量:静态数据段 .data段
空间权限之边界访问
空间访问权限要考虑哪些?
-
读写权限
-
边界要求
- 无越界检查
char buf[4]; int a=0x123456789; buf[0]=0x11; buf[1]=0x22; buf[2]=0x33; buf[3]=0x44; buf[4]=0x99; printf("the a is %x\n",a);
我们只定义了四个数组,而这第五个数组就越界了。
以后工作的时候不需要画图考虑,可能先入a也可能先入buf,只要考虑它越不越界。
即使你在int a前面加上const, a的值还是会被改。因为const是一个建议符号,即在编译的时候,告诉编译器如果把这个值放到等号的左端,就不要改了。在编译的时候保证它只读,而在运行的时候,不归const管了,运行的时候值本身就是内存,天然是可读可写的,因为这属于栈空间,操作系统也不会管,而越界是可以修改的。
- 如何定义边界
- 数量
- 特殊结束符号—–>字符串 特殊结束标志 C编译器认可
字符串空间的行为
有默认的结束标志:数字0 == ‘\0’
int main()
{
char*s1="123456";
printf(" the s1 size:%d ; the str size:%d",sizeof(s1),sizeof("123456"));
}
s1是把钥匙在64位体系中字节是8;对于字符串C语言自动的会在它的结尾,加上一个‘\0’