linux c的指针与内存

指针

指针的声明用*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还会对多个程序进行统一调度、分配内存空间,防止不同程序对于内存的相互侵占。

linux c的指针与内存_第1张图片
64位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 
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语言字符串是原始字符串,其实就是字节数组。

你可能感兴趣的:(linux c的指针与内存)