指针
指针的声明用*a来表示,指针是保存内存地址的一种数据类型。另外,取地址用&a来做。
指针的调用和传值
int a = 100;
int point_t(int *a)
...
point_t(&a)
内存
计算机可以控制、接收电流的高(1)低(0)电位或者通(1)断(0)电路,这就产生了计算机能识别的二进制。
内存的最小单位是字节(Byte,1 Byte = 8 bits,一个字节是八个二进制位),一个字节可以表示00000000至11111111种意义(可以表示0~255元素)。
一个十六进制的数字可以表示4位二进制的数字(1111 = 0xf)。一个字节,既可以用8个二进制数字表示,也可以用2个16进制的数字表示。
32位操作系统
32位os的cpu地址总线是32位,支持232 个地址位,暨寻址空间是32位,最多能访问0~232次方(大约4GB空间)个内存地址,可以理解为只能访问并操纵最多4GB的内存空间。每个内存地址位记录一个唯一的内存地址编号,每个地址编号对应存储一个字节。
证明:
232=210 * 210 * 210 * 2 2
= 1024 byte * 1024 byte * 1024 byte * 4
= 1kb * 1024 byte * 1024 byte * 4
= 1mb * 1024 byte * 4
= 1gb * 4 = 4gb
内存管理
内存由os统一管理(编号、规划内存),内存大小的也会将多根内存条合并后统一计算。os还会对多个程序进行统一调度、分配内存空间,防止不同程序对于内存的相互侵占。
例如,64位os,应用程序使用前48位,暨0x7fffffffffffffff以下的内存地址位,系统内核使用后边的16位内存地址位。
这样把用户的内存和os的内存隔离开,还有个好处就算,os的内存不会被大量占用,避免机器卡住、卡死、死机等状态的发生。因此,只要os可用,就可以用os将关闭应用程序,解决安全等问题。
内存规划
如上图所示,os把内存进行了分片,分为系统内核、栈、自由内存区、堆、数据段、代码段
代码段
代码被编译后,会存到磁盘中,形成2进制文件。运行这个程序(例如,调用main函数),其实就是os把二进制数据文件(也就是计算机指令)加载到内存代码段中。C语言不能直接操作代码段的地址。
在代码段,可以知道程序的二进制字节大小。例如定义如下函数:
p &rect 0x4005a6
p &quadrate 0x4005dd
用quadrate 的地址高度减去rect的地址高度,就是加载到内存中rect函数的大小
数据段
数据段存储静态变量、全局变量、常量,他们都会分配独立的地址,且地址唯一
堆内存
栈内存
栈内存记录变量的地址和值,还会记录程序已经执行了多少行的行号,记录程序当前状态,调用哪个函数,函数中有哪些变量,变量值。
变量名其实就是地址别名,变量其实就是内存地址,系统通过变量告诉cpu要到哪个地址取数据。
#include <stdio.h>
int global = 0;
int rect(int a, int b)
{
static int count = 0;
count++;
global++;
int s = a * b;
return s;
}
int quadrate(int a)
{
static int count = 0;
count++;
global++;
int s = rect(a, a);
return s;
}
int main()
{
int a = 3;
int b = 4;
int *pa = &a;
int *pb = &b;
int *pglobal = &global;
int (*pquadrate)(int a) = &quadrate;
int s = quadrate(a);
int s2 = (*pquadrate)(a);//定义一个函数指针
printf("%d\n", s);
return 0;
}
操作系统对于内存的管理
代码端
gcc编译器对于代码是如何优化的
函数栈就不一样了,一个函数可用被多次调用,一个变量也会被多次调用
gcc会优化存储
我们在 中看到的地址,例如0x7fffffffddfc就是一个整型变量的首地址,然后ddfc、ddfd、ddfe、ddff都是这个整形变量的地址。(因为,一个整形变量占用四个字节,四八32,一个整形是32bits)
但是 gcc会做优化
不连续赋值,编译器会优化,为了让cpu操作指令更方便更快,为了提升程序的执行效率,因此,会对源代码进行优化。那么编译之后的指令存储,跟我们编写的代码的顺序可能会不一样。例如,会先定义基本类型的,然后将基本类型的地址连续放到内存中,然后再放其他类型的,都是把同一类型的变量放到一起。这样可用让程序执行更快,至于为什么,下面会说。另外,64位操作系统下,指针占8个字节(32位操作系统,指针占4个字节),因为,是64bits,一个字节存8个bits,因此,需要8个字节才能将全部的地址存储下来。
栈这个内存空间,保存的就是函数当前运行的状态。包括代码执行到多少行指令,每一个内存空间到底是什么类型的数据,什么数值。在栈中,函数或者变量会被多次调用,每次调用都会分配新的独立的栈地址。函数调用就是先进后厨的栈内存结构。因此,越到栈顶的函数的地址越小。栈是从后向前压栈的。栈,越往后调用的地址越小。从栈顶向下分配的。
函数指针
函数指针定义后,也可以调用函数,这种做法经常用在我们做 回调函数来使用
一个地址,用括号包起来,表示指向一个代码段的函数。
带*号表示要访问地址对应的字节块是啥
数组、动态堆类型创建、指针运算
数组申明的内存排列
C语言中的数据声明,也是在栈内存中保存的
c语言不做指针代码的安全检测。因为,地址是连续排放的,因此,指针运算符就用上了
指针运算
指针偏移,的运行效率非常高,性能非常好,这样比cpu根据地址总线取地址要更方便一些
int *p=&a;
p+=3;表示将指针偏移3个int 变量占位符。
在指针上做的加个减,其实做的是地址的偏移。
数组其实就是一种地址的表示
int array[3];
可用表示为,int p = array;
此时,p也就指向了array数组的第一个元素的地址。
因此我们这样写
p[0] == array[0];
p[1] == array[1];
p[2] == array[2];
因此,在c中,任何用数组操作的地方,都可以用c来代替。因此,数组是一种指针常量。但是 ,指针是指针变量。因此,定义了数组,数组就永远指向某个地址
但是 ,指针指向的地址是会改变的
这就是区别。
字符数组和指针字符串
字符串 指针 竟然 存在 代码段?
注意:c语言字符串数组,以\0结尾,什么时候遇到\0也就标识着字符串结束了。
malloc可以分配地址
c语言字符串是原始字符串,其实就是字节数组。