指针
指针概述:内存类型资源的地址、门牌号的代名词
指针只是个概念,要用还得用指针变量:存放指针这个概念的盒子
C语言编译器对指针这个特殊的概念,有2个疑问?
1、分配一个盒子,盒子要多大?
在32bit系统中,指针就是4个字节,指针大小都固定了,就是4字节,跟你指向什么类型没有关系
2、盒子里存放的地址 所指向 内存的读取方法是什么(指向这段内存的空间大小)?
char *p; char是修饰*的,这样定义就告诉p一次只读一个字节
int *p; int就代表p一次只读4个字节,这样就完成了声明和定义
int *p1;
char *p2;
printf("the p1 is %lu,the p2 is %lu\n",sizeof(p1),sizeof(p2));
打印出的结果p1、p2都是4
printf("the p1 is %lu,the p2 is %lu\n",sizeof(*p1),sizeof(*p2));
结果是4、1,一次读4个字节、1个字节,也可以理解为内容是4个字节、1个字节(指向这段内存的空间大小)
int a = 0x12345678;
int *p1;
p1 = 0x1122; //这样的写法是可以的,1122这个数肯定可以放到32bit的盒子里,是可以赋值进去的
但是!赋值进去后,属性发生了改变,原来代表数字,赋值进去后代表了地址,
而1122对应的资源是谁呢,资源合不合法呢?
所以,指针指向内存空间,一定要保证合法性,保证这个空间确实存在且能读能写
p1 = &a; 这样才正确,&a是a的门牌号
printf("the p1 is %x\n",*p1); //the p1 is 12345678 *p是取内容
当:
int a = 0x12345678;
char *p1;
p1 = &a; //a的地址一定是门牌号的最小值
printf("the p1 is %x\n",*p1); //the p1 is 78 *p是取内容

小端模式:
数据的低位放在低地址空间,数据的高位放在高地址空间
简记:小端就是低位对应低地址,高位对应高地址
大端模式:
数据的高位放在低地址空间,数据的低位放在高地址空间
简记:大端就是高位对应低地址,低位对应高地址
p、*p、&p的区别
如定义:
int a = 10;
int *p;
p = &a;
则:
p:因为p = &a;所以就是a的地址
*p:p的内容,就是a的值
&p:p自己本身的地址
二级指针也同理:
图示如下:

指针+修饰符
const:常量、只读(不能变)
const char *p; //const修饰char,一个字节只读方式来读取内存,更倾向于第一种写法
char const *p;
char * const p; //const修饰p,p指向的是一个地方,不能再变了,更倾向于第一种写法
char *p const;
const char * const p; //指向的地址不能变,内容也不能变;希望产生ROM空间时用
const char *p; //p所指向的内存空间是只读的,p可以指向任意的只读空间,但不能对p里的内容进行操作
就可以理解为字符串,"hello world",内容是永远不变的,字符串的不可变性
看到:const char *,第一反应就是字符串
char * const p; //p指向的是一个固定地址,里面的内容是可以修改的
与硬件资源相关的,如LCD显卡缓存
段错误:指针指向的内容被非法访问了
#include <stdio.h>
int main ()
{
char *p = "hello world!\n"; //指针指向字符串时,规范写法:const char *p = "xxx"
//""相当于整型常量,不可变的
printf( "the one is %x\n",*p); //打印出68,即h的ASCII码
*p = 'a'; //非法访问,出现段错误
printf( "the %s\n",p);
}
char *p = "hello world!\n"; //这段内存不可变
char buf[] = {"hello world!\n"}; //buf是可变的,buf属于main函数的变量内存
char *p2 = buf;
*p2 = 'a'; //这样就没问题
printf( "the %s\n",p2); //the aello world!
volatile:防止优化指向内存地址,多跟硬件有关
volatile char *p;
*p == 0x10; //这样编译器就会不断地从指向的内容去读,而不会优化掉
typedef
char *name_t; name_t是一个指针,指向了一个char类型的内存
typedef char *name_t; name_t是一个指针类型的别名,指向了一个char类型的内存
name_t abc;
指针+运算符
++、--、+、-
int *p = xxx; [0x12]
p+1 [0x12 + 1*(sizeof(*p))]
指针的加法、减法运算,实际上加的是一个单位,单位的大小可以使用sizeof(p[0])
int *p p+1
char *p p+1 两者的结果是不同的
p+1 p-1
p+1时:p不变,以p位参考位置,向上或向下查看,只是看
p++ p--:不仅仅是看内容,把p的内容都更新了
[]
变量名 [ n ]
n:ID号,标签
地址内容的标签访问方式
如:p[2],p为基地址的第2块内容
p+n p+n得到的是地址
p[n] 访问的是内容,加了[]后自动会把内容取出来
两种写法都是一致的
变量分配
是从高往低分配的,栈区,从上往下
int main()
{
int a = 0x12345678; a在高地址
int b = 0x99991199; b在低地址
int *p1 = &b;
char *p2 = (char *)&b; //加上强制类型转换,可以不报指针类型与数据类型不符合的警告
printf("the p1+1 is %x,%x,%x\n",*(p1+1),p1[1],*p1+1);
printf("the p2+1 is %x\n",p2[1]);
}
char *p2 = (char *)&b;
相当于分两步写:p2=&b 两边再加上char *
类型没有变化,大小还是那个大小,只是大小的属性不一致了
p1+1只是得到了a的地址(门牌号),*(p1+1)才是取内容(gcc编译器是连续存放的),其他的不一定
*(p1+1)和p1[1]是一样的,都是内容
*p1+1 是把内容先取出来,再加1,会得到0x9999119a
p2[1]的结果是11
指针越界访问
int main()
{
const int a = 0x12345678; //a在高地址
int b = 0x11223344; //b在低地址
int *p1 = &b;
p[1] = 0x100;
printf("the a is %x\n",a); //就可以越界修改a的值
}
逻辑操作符
指针用的最多的还是==、!=
1、跟一个特殊值进行比较 0x0:地址的无效值,结束标志
if(p==0x0),往往我们会写一个宏:
if(p==NULL),从语言编译器一边会预定义好NULL的值,
2、指针必须是同类型的比较才具有意义
char * int * 是不同种类型
多级指针
int **p 指向指针的指针
首先找到这行的关键字:p开头,然后左边有一个*,就告诉我们p是*(p是一个箭头),那么p就一定是地址
往外继续看,发现*p左边还有*,说明p一次读4个字节
p里面存放的内存还是一片连续的空间,这片连续空间的限制大小是4个字节,只不过这4个字节非常特殊,还是指针,这个指针就是以int决定的来读取内存
p开头,然后左边有一个*,就告诉我们p是一个箭头,这个箭头里头存什么是由第2个*决定的
说明p里面存放的还是一个箭头,这个箭头该如何操作呢,就是由int决定的
如:char **p;

p[0] p[1] ... p[n]
当某一个p[m] == NULL时,意味着这段二维空间就结束了
多级指针就是一段连续空间,这样的连续空间保存着其他空间的地址,把不连续的空间变为连续
#include <stdio.h>
int main(int argc,char **argv) //main函数的标准写法就是这样
{
int i;
for(i=0;i<argc;i++){
printf("the argv[%d] is %s\n",i,argv[i]);
}
return 0;
}
gcc -o build 005.c
执行./build
结果为:
the argv[0] is ./build
执行./build hello 123 456
结果为:
the argv[0]is ./build
the argv[1]is hello
the argv[2]is 123
the argv[3]is 456
二级指针还有一个常用的模板:
#include <stdio.h>
int main(int argc,char **argv) //main函数的标准写法就是这样
{
int i = 0;
while(argv[i] != NULL){
printf("the argv[%d] is %s\n",i,argv[i]);
i++;
}
return 0;
}
执行./build hello 123 456
结果还是一样:
the argv[0]is ./build
the argv[1]is hello
the argv[2]is 123
the argv[3]is 456
数组
数据类型 数组名[m]
m的作用域是在申请的时候
int a[100]; 定义了100个int大小的元素
数组名是地址常量,指针是表示地址的变量
数组名一定不能放到=的左边 ==大忌:a === 也不可以写a++,因为a++等价于a=a+1
char buf[100];
buf = "hello world!"; 错误!!
数组名就是一个常量标签,就像函数名叫main,不管怎么运行都不变
数组的细节知识:
a是数组首元素的地址,就是指向首元素的指针,a等于&a[0];&a是数组首地址;a和&a值是一样的
a+1:就是数组首地址加上一个元素所占的地址大小,这里int是4个字节,所以加上1x4
&a+1:代表的是加上整个数组的大小,这里数组尺寸是3,所以+1代表的是地址加上3x4,指向数组末尾的位置
* (a+1):代表的是数组第一个元素的值,就是a[1]的值,不再是元素的地址
*(&a+1):将&a+1地址取出来
&a+i = a + i*sizeof(a);
a+i = a +i*sizeof(a[0]);
数组空间的初始化
初始化实际上就是空间的赋值问题,按照标签逐一处理:a[0] = xx,a[1] = yy,...,a[n] = zz
int a[10] = 空间;
int a[10] = {10,20,30}; {}就代表空间,是一个块
===>a[0] = 10; a[1] = 20; a[2] = 30; a[3] = 0; 后面不写的默认把0赋进去(具体看编译器,也可能是随机值)
数组空间的初始化和变量的初始化本质不同,尤其是在嵌入式的裸机开发中,空间的初始化往往需要库函数的辅助
char buf[10] = {'a','b','c','\0'};
buf当成普通内存来看,没有问题
buf当成一个字符串来看,这种赋值方式一定要在最后加上一个'\0',也就是0
字符串的重要性,结尾一定有个'\0',内存中就是0的表现形式
char buf[10] = {"abc"}; 是buf当成字符串空间来看最合理的写法,C语言编译器看到""会自动加\0 所以实际上这里是4个字节了
char buf[10] = "abc"; abc这个内存逐一地拷到buf中,系统会有两个abc,一个常量的abc,一个变量abc
buf[2] = 'e'; √没问题
char *p = "abc"; 用指针去指常量区域
p[2] = 'e'; ×肯定出现段错误,因为p指向常量,不能对常量进行修改
char buf[] = "abc"; 4个字节
char buf[10] = "abc";
假如我想要让: buf = "hello world" 怎么办,当然这种写法是大错特错!!
这就是第二次内存初始化,或者说第二次赋值,只能是逐一处理
buf[0] = 'h' buf[1] = 'e' ... buf[n] = 'd' buf[n+1] = 0;字符串的赋值一定要注意0的赋值
strcpy、strncpy
当把一块空间当成字符空间时,C语言提供了一套字符拷贝函数
字符拷贝函数的原则:一旦空间中出现了0这个特殊值,函数就即将结束了,不管0在哪里,看到就结束了
strcpy():
char buf[10] = "abc";
//假如我想要让: buf = "hello world"
strcpy(buf,"hello world"); 但是在工程中绝对不要用此函数,因为会导致非常严重的内存泄露问题!!!!万一字符串很长,就会一直拷贝,不止10个元素了,就会出问题
应该使用 strncpy()函数
strncpy(buf,"hello world",n); n限制了拷贝的数量
字符空间
ASCII码编码来解码的空间,就是给人看的
用%s来看的,比如人写abc时,在计算机内存中是用'a','b','c'来表示的,就是char类型,char类型的编码方式就是ASCII码
\0作为结束标志
非字符空间
当做数据采集时要存储数据 0x00 - 0xff 8bit
形成了一个不成文的规定:
char buf[10]; ---->string
unsigned char buf[10]; ---->data
非字符空间的拷贝三要素:1、源 2、目的地 3、个数
C语言提供了一个函数:memcpy(dest, src, n)
int buf[10];
int sensor_buf[100];
memcpy(buf,sensor_buf,10*sizeof(int)); //或者用sizeof(buf)最保险
unsigned char buf1[10];
unsigned char sensor_buf[100];
memcpy(buf1,sensor_buf,10*sizeof(unsigned char));
注意:不要用strncpy(),如果一开始就有0值,那么buf就一直为空!
指针数组
由指针构成的数组
char * a[100];
sizeof(a) = 100 * 4; //每个指针是4个字节
char **a;
看到char **a,就可以通过标签a加下标:a[0]、a[1]这样的操作去访问每一个元素,然后每一个元素里面的内容就可以访问的到
多维数组
数组名的保存
定义一个指针,指向int a[10]的首地址
int *p1 = a; 没有任何问题
定义一个指针,指向int b[5] [6]的首地址
int **p2 = b; 一次读4个字节而已,并不匹配
int (*p2)[6] = b; 这样才对!
二维数组读内存的方法是一行一行读。5行6列,一次要读6个int
int *p[5]; 指针数组,好多个指针指向不同的块
int (*p)[5]; 数组指针,一个指针,一次性可以读5个块,5个int的方法读内存
数组指针和指针数组的区别
指针数组是指针类型的数组,里面的元素是指针类型的数据
数组指针(行指针)是一个指针变量,似乎是C语言里专门用来指向二维数组的,它的p+1表示到下一行。
int b[2] [3] [4];
int (*p)[3] [4];
结构体
#include <stdio.h>
struct abc{
char a;
int b;
};
int main ()
{
struct abc buf;
printf ("the buf is %lu\n",sizeof(buf));
}
>>the buf is 8
为了效率,希望牺牲一点空间换取时间的效率
#include <stdio.h>
struct abc{
char a;
short e;
int b;
};
struct my{
char a;
int b;
short e;
};
int main ()
{
struct abc buf;
struct my buf1;
printf ("the buf is %lu:%lu\n",sizeof(buf),sizeof(buf1));
}
>>the buf is 8:12
short是两个字节,因此第一种定义方式:1 0 1 1;1 1 1 1 只需要2个字节
第二种定义方式:1 0 0 0;1 1 1 1;1 1 0 0 需要3个字节
内存分布图
编译--->汇编--->链接
#include <stdio.h>
int main()
{
int a;
a = 0x10;
printf("the a is %p\n",&a); 用%p看地址会合理一些,它会自动给加上0x
printf("the is %p\n", main);
return 0;
}
>>the a is 0xbfcle8cc 局部变量在高内存区域
>>the is 0x80483e4 函数在低内存区域
#include <stdio.h>
int a;
int main()
{
a = 0x10;
printf("the a is %p\n",&a); 用%p看地址会合理一些,它会自动给加上0x
printf("the is %p\n", main);
return 0;
}
>>the a is 0x804a01c
>>the is 0x80483e4 当a为全局变量时,此时两者都在低段地址上了
内存分布图:

只读空间
代码段、只读数据段、全局变量空间都是整个程序结束后才消失,生存周期最长
静态空间,C语言编译时已确定
#include <stdio.h>
int main()
{
int a;
unsigned char *p;
a = 0x10;
printf("the a is %p\n",&a); 用%p看地址会合理一些,它会自动给加上0x
printf("the is %p\n", main);
p = (unsigned char *)main;
printf("the p[0] is %x\n",p[0]);
p[0] = 0x12; //出现了段错误
printf("+++++++ the p[0] is %x\n",p[0]);
return 0;
}
>>the a is 0x804a024
>>the is 0x80483fa
>>the p[0] is 55 //读没有问题,而且是一个字节一个字节地读
>>Segmentation fault //出现了段错误
#include <stdio.h>
int main()
{
char *p1 = "helao world";
printf("the p1 is %s\n",p1);
printf("the string address is %p\n", "helao world");
p1[3] = 'l'; //出现了段错误,因此字符串不能修改
printf("the p1 is %s\n", p1);
return 0;
}
>>the p1 is helao world //可以看,没问题
>>the string address is 0x80485a0
>>Segmentation fault //出现了段错误
''是个数,给人描述逻辑或者字符时会用到
""是一片空间,是地址的属性,不是值的属性
栈空间
运行时,函数内部使用的变量,函数一旦返回,就释放,生存周期是函数内
size命令 – 显示文件各段大小 size 文件名
text:代码段
data:已初始化地数据段
bss:未初始化的数据段
dec = text + data + bss
hex:为dec的十六进制
strings命令:把双引号里的东西打印出来 strings 文件名
nm命令:查看可执行程序的标签 nm 文件名
static修饰的就只在函数内有效,局部有效,但是存放还是在全局数据段中
堆空间
运行时,可以自由,自我管理地分配和释放地空间,生存周期由我们来决定
分配和释放必须配对
分配:
malloc()一旦成功,返回分配好的地址给我们,只需要接收即可;对于这个新地址的读法,由我们灵活把握
输入参数指定分配的大小,单位就是B(字节)。
如要申请一个int a[5]; 就要malloc(5*sizeof(int));
char *p;
p = (char *)malloc(100);
if(p == NULL){ //一般情况下需要对p为空进行判断,进行处理或者忽略,防止申请失败
error...
}
malloc申请空间运行一次就申请一次,函数返回后,p没有了,但是p指向的空间还有
释放:
free(p); 释放malloc申请的地址,不释放就会导致内存泄露
p = NULL; 释放后应将指针设置为NULL,防止悬挂指针的出现

浙公网安备 33010602011771号