字节
本文我们操作的最小的数据存储单位是字节
变量和变量名
1.当我们声明一个变量的时候,内存中就为我们开辟了一块空间,用于储存之后赋予变量的值,此时内存要解决两个问题:1. 存在哪里,2.开辟多大的空间,其实这两个问题在我们的语句中都有:其中变量名告诉我们把内存空间开辟到哪里,但是其实这个说法也不准确,准确来说是经历了以下步骤
- 我们告诉了操作系统我们需要一块内存,这块内存的大小是int
- 操作系统根据实际情况为我们分配了一块空间,这块空间可能是一个字节,也可能是很多个字节,具体的看我们的数据类型。这种分配可以如下图的自动分配,也可以是使用
malloc 函数手动分配 - 当分配完成之后,操作系统会把分配空间的首地址和我们的变量名建立一个唯一映射,我们使用变量名就可以访问到这片内存空间中的值。
int i
各种变量类型所占据的空间(按字节)
我的操作系统是64位的,各种数据类型所占据的空间(字节)如下
size of int is 4
size of long is 8
size of char is 1
size of float is 4
size of double is 8
size of int* is 8
size of char* is 8
size of int** is 8
注意所有不同数据类型的指针的大小都一样,无论是字符指针,整形指针,不同级数的指针大小也都一样,比如一级指针和二级指针,还有多级指针。但是为什么呢?因为我们说变量名是地址,而所有的变量,不管其内存多大,变量名都是等于该变量占据所有空间的首地址。所以所有指针的大小都是一样的
内存
内存是线性的,但是为了方便,我们通常把内存画成一个矩形,矩形的宽为一个字节,每一行都赋予一个唯一的地址,如图,每个小格子就是数据的最小存储单位,比特。每一行是一个字节。
定义变量和调用函数的内存行为
void swap(int* a,int* b){
int temp = *a;
*a = *b;
*b = temp;
}
int main(){
int a = 1234 ;
int b = 5678 ;
swap(&a,&b);
}
以上是一个经典的交换函数,我在这里画了内存中的行为
int a = 1234 ;
int b = 5678 ;
这两条语句会在内存中开辟两个int空间(8字节),同时在对应的比特位填充数据。当我们调用swap函数的时候
swap(&a,&b);
我们在内存中开辟两个指针空间(8字节)用于接受传进来的地址。
从访问权限看函数传参
- 如果函数是值传递,那么我们对该值的权限只有
读 ,我们可以使用printf 函数输出值,可以将值作为运算数进行运算 - 如果传的的是一个指针p,那么我们对该指针指向的对象
*p 具有读写 的权限,我们可以改变其值,比如上面的swap函数就是地址传递
指针变量的赋值
指针之间的赋值相当于共享某空间的地址,参看下面代码
int a=10;
int* p = &a;
int* q = p;
printf("[*p] is %d,[&p] is %p\n",*p,p);
printf("[*q] is %d,[&q] is %p\n",*q,q);
以上代码的输出是
[*p] is 10,[&p] is 0x7ffe0458c994
[*q] is 10,[&q] is 0x7ffe0458c994
如果要修改指针本身所指向的地方,我们需要使用二级指针,比如下面,指针p,q,r 可以修改变量a的值,pp,qq,rr 可以修改p,q,r 指针的内容,换言之修改指针的指向,比如现在我想让指针p指向一个新的变量,那么我们需要操作二级指针。
int a= 10;
int b= 20;
int* p = &a;
int** pp = &p;
printf("%4s %4s %14s %14s %14s%4s %14s %14s %14s %14s\n","a","b","&a","&b","p","*p","&p","pp","*pp","&pp");
printf("%4d %4d %14p %14p %14p%4d %14p %14p %14p %14p\n",a,b,&a,&b,p,*p,&p,pp,*pp,&pp);
*p = 19;
printf("%4d %4d %14p %14p %14p%4d %14p %14p %14p %14p\n",a,b,&a,&b,p,*p,&p,pp,*pp,&pp);
*pp = & b;
printf("%4d %4d %14p %14p %14p%4d %14p %14p %14p %14p\n",a,b,&a,&b,p,*p,&p,pp,*pp,&pp);
*p = 30;
printf("%4d %4d %14p %14p %14p%4d %14p %14p %14p %14p\n",a,b,&a,&b,p,*p,&p,pp,*pp,&pp);
输出内容是
a | b | &a | &b |
---|
10 | 20 | 0x7ffc9bd71c90 | 0x7ffc9bd71c94 | 19 | 20 | 0x7ffc9bd71c90 | 0x7ffc9bd71c94 | 19 | 20 | 0x7ffc9bd71c90 | 0x7ffc9bd71c94 | 19 | 30 | 0x7ffc9bd71c90 | 0x7ffc9bd71c94 |
p | *p | &p |
---|
0x7ffc9bd71c90 | 10 | 0x7ffc9bd71c98 | 0x7ffc9bd71c90 | 19 | 0x7ffc9bd71c98 | 0x7ffc9bd71c94 | 20 | 0x7ffc9bd71c98 | 0x7ffc9bd71c94 | 30 | 0x7ffc9bd71c98 |
pp | *pp | &pp |
---|
0x7ffc9bd71c98 | 0x7ffc9bd71c90 | 0x7ffc9bd71ca0 | 0x7ffc9bd71c98 | 0x7ffc9bd71c90 | 0x7ffc9bd71ca0 | 0x7ffc9bd71c98 | 0x7ffc9bd71c94 | 0x7ffc9bd71ca0 | 0x7ffc9bd71c98 | 0x7ffc9bd71c94 | 0x7ffc9bd71ca0 |
说说链表的malloc
在刚开始写链表的时候,我一直纠结一个问题,那就是为什么我们需要在创建链表的时候传递二级指针,如下,我们定义了一个结构体,然后写了一个创建链表的函数
typedef struct Node{
int data;
char name[20];
char nodeName[5];
struct Node* next;
struct Node* pre;
}Node;
void Create(Node** node){
(*node) = (Node*) malloc(sizeof (Node));
(*node)->next=NULL;
(*node)->pre=NULL;
(*node)->data=0;
strncpy((*node)->nodeName,"Head",5);
strncpy((*node)->name,"Head",5);
}
为了解决这个问题,我们需要说说malloc函数,malloc函数会分配一块内存空间,然后返回一个指针 ,而注意,在这个过程中,我们需要修改的是指针的值 ,而我们说过,在函数的参数传递中。
如果想要改变变量的值,需要传递指针 如果想要改变指针的值,需要传递指针的指针,也就是二级指针
以下为了说明简便,我使用int** 作为传递的指针类型,且看如下代码
#include "stdio.h"
#include "stdlib.h"
void Create(int** p){
*p = (int*) malloc(sizeof (int ));
*(*p)=20;
}
int main(){
int* q;
Create(&q);
printf("%d",*q);
}
过程是这样的
- int* q,创建一个int指针,但是这个指针指向未知
- 通过函数传递二级指针,建立p和q的映射关系(*p=q)
- 通过
*p 来改变q的值,接受malloc的返回值 - 通过
*(*p) 来访问malloc的int空间,并且修改其值
如果把程序稍微改变一下,大家来观察下不同
#include "stdio.h"
#include "stdlib.h"
void Create(int** p){
int* pointer = *p;
pointer = (int*) malloc(sizeof (int ));
*(pointer)=20;
}
int main(){
int* q;
Create(&q);
printf("%d",*q);
}
看似小小的改变,但是产生了完全不同的结果
- 创建int指针,但是不知道指针指向哪里
- 通过函数传参建立了p和q的关系(p=*q)
- 创建了一个局部int指针pointer指向*q,也就是未知的空间
- 局部变量pointer接受malloc返回值
- 访问局部变量pointer指向的地方,赋值。
我们发现其实q的值并没有发生变化,问题就出现在我们使用了int* pointer = *p; ,我们需要修改的是p的值,所以我们需要传递p的指针,而p本身是一个int指针,所以我们需要传递二级int指针,而我们使用int* pointer = *p; 语句,则是直接让我们的操作只能对q指向的位置空间操作,或者改变pointer的值,本操作直接令访问权限发生了变化。而且我们也不能在通过对pointer 取地址运算重新操作*(Addrq) ,我们之后的一切操作都是针对局部变量pointer(见上图)。
降低指针权限,安全操作
看了上面,那时不是就说明int* pointer = *p; 语句就一无是处呢?很显然不是,因为指针的可以任意访问,所以指针级数越高,也就越危险。我们传递二级指针是为了可以接受malloc的值,而之后的操作,绝大部分时间,都是读操作 ,所以我们可以尝试在使用二级指针接受malloc返回值之后,进行指针降级操作。如下:
#include "stdio.h"
#include "stdlib.h"
void Create(int** p){
*p = (int*) malloc(sizeof (int ));
int* pointer = *p;
*(pointer)=20;
}
int main(){
int* q;
Create(&q);
printf("%d",*q);
}
数组名和指针
在学习数组的时候我们知道,数组名其实就是指针,而对数组的下标索引操作,也可以使用指针的偏移来实现,比如
int a[10]={0};
函数指针
(未完待续。。。)
|