ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

C语言:指针补充

C语言:指针补充

指针降级:

降级是数据名的隐式降级

二维数组 int arr[3][4]

数组名会在绝大多数的表达式中隐式降级(数组名不显示)

即int[3][4];它等价于:int(*)[4];(指向一行4个int的行指针)

于是有:

表达式真实类型含义
arrint(*)[4]指向第0行(一行4个int)
arr[0]int *指向第0行第0个int元素

补充区别:

int arr[3][4]:栈上分配3行×4列连续内存,等价于一维数组

int (*)[4]:只是一个8字节指针(64位),仅存行首地址,不分配数组空间

常量区开辟空间的方法

一、常量区(只读数据段.rodata)存放内容

常量区内存只读,程序加载时由操作系统分配,生命周期全称跟随进程,不能手动malloc开辟,只能通过语法把数据放入该区

1.字符串字面量(最常用)

用双引号包裹的字符串,自动存常量区

char *str = "hello world";

禁止修改:str[0] = 'H'会触发段错误,常量区不可写

而数组形式:char arr[] = "hello";//数组内容负责到栈内存,栈可以修改,不在常量区

2.const修饰全局变量/static全局

const int g_val = 100;

const char g_buf[] = "abc";

不加const的全局变量:存.data读写数据段

局部函数内const int a=10:大多放栈,不进常量区

3.枚举常量

枚举值编译区确定,符号对应数值存放常量区

4.浮点字面量、整形字面量(编译嵌入指令/常量区)

double d = 3,1414//3.1415浮点常量存入rodata

int x = 999999;//大数常量放入常量区

5.static const静态常量

静态全局/静态局部const,放入常量区

void test()

{

//static const存rodata;单纯const存栈

static const int num = 666;//存常量区

const int length = 20;//存栈

}

static const float pi = 3.14;//存常量区

写法内存区域可写
"test"字面量/char *p="test".rodata 常量区只读
char arr[]="test"栈 / 全局 data可修改
malloc分配字符串堆 heap可修改

(1)为什么错了?

因为p是一个一级指针,这个指针开辟的空间在堆区,p的作用域只在GetMemory这个函数内部,在Test函数里调用GetMemory这个函数,并没有改变str的地址,传str进去是传值,在GetMemory中是改变的是形参的值,开辟空间是改变了p的值,从NULL值改变成堆地址,函数返回后,p的内存被销毁,但实参str的NULL不变,并没有改变实际的str;因为p在调用结束之后是在另一块空间,只在GetMemory这个函数内部才能使用,Test里面的str指向的地址还是NULL;

str是一个指针,如果直接在GetMemory里传str,就是把NULL这个地址传给p,而p在GetMemory里又开辟了一个空间,并没有修改str的内存里的值,str的内存地址还是NULL,不是合法地址,NULL不能用来赋值,存数据

(2)为什么错了?

char p[]="hello world";//这个p是在栈区;调用完这个函数的时候就结束了,在Test里面调用GetMemory这个函数,没用把p的地址传进来,p使用完就回收了,这个字符串传不进来;要使Test函数能访问这个p,就要把p这个数组改成指针,*p="hello world";就让p指向常量区,函数返回的是p这个指针,指针执行的地址是常量区,一直存在不会被销毁,(生命周期是整个程序运行期间)所以printf可以通过这个地址找到字符串

func函数里的ptr是局部变量,它的地址调用完就销毁了,而main里的ptr是另一个地址空间,它们不指向同一块内存,在main里面调用func函数,ptr的地址还是原来的取i的地址,

结构体大小的计算:

1.结构体的总大小一定是结构体内最大内置类型的倍数;(内置类型只能为char、int、long(4字节)、long long(8字节)、double、short、float、指针这几种,不能是struct student这种类型,他只能是基本类型)

嵌入式内存芯片的存储大小很小(一般是几M),要节省空间就要改变结构体内存排布先后顺序实现缩小空间

2.结构体的每个元素的位置一定是这个元素类型大小的倍数

联合体:联合体里的多个成员共享同一块内存,这块内存大小是最大的一个成员类型的内存;

union D

{

int a;

char ch;

};

D d;

d.a = 0x12345678;

if(d.ch == 0x78)//第一个字节是共享的

{

printf("小端\n");

}

else

printf("大端\n");

return 0;

}

sizeof(buf)是buf的内存大小,而不是存的实际字符串大小;代表整个数组的大小,不代表首地址;

字符串本质是一个地址值,在常量区;buf[100]是在栈区,而p是在常量区

memset(buf,0,sizeof(buf));//将buf这个数组的元素都置为零

typedef给已有的数据类型起一个别名;

成环链表的书写只能用int作为返回值;用void会判断错误,int作为返回值:1代表成环;0代表不成环

Int IsLoop(Node *head)

{

if(head == NULL || head->next == NULL)

{

printf("不成环\n");

return 0;

}

Node *fast = head->next;

Node *slow =head->next;

for(fast,slow;fast->next!=NULL,fast->next->next != NULL;slow = slow->next,fast = fast->next->next)

{

if(fast == slow)

{

printf("成环\n");

return 1;

}

}

printf("不成环\n");

return 0;

}

函数声明是( *f());//f与*没括在一起就不是指针(指针变量);

括在一起(*f)();//是函数指针;

void * (*f)(void*);//是一个指向void *类型参数的指针;返回烈性为void *;

memcpy与strcpy的区别

memcpy的语法:

函数原型:void *memcpy(void *dest, const void *src, size_t n);

strcpy的语法:

函数原型:void *strcpy(void *dest, const void *src);

1. 拷贝结束的依据(最核心的区别!)

  • memcpy:拷贝时完全不看内容,只看长度它把第三个参数指定的字节数严格搬运完,就立刻停止。哪怕数据中间包含了无数个0x00,它也照搬不误。

  • strcpy只认字符串结束符\0(即二进制里的0x00。它从源地址开始读,一旦遇到0x00就认定“字符串结束了”,立刻停止拷贝。

2. 参数数量不同

  • memcpy3 个参数memcpy(目标地址, 源地址, 拷贝的字节数)

  • strcpy2 个参数strcpy(目标地址, 源地址)。因为它不需要知道长度,它靠自己去找0x00来结束。

注意:为什么用strcpy处理Modbus是一个致命的杀手

因为ModbusRTU的底层是一个纯二进制,一个合法的Modbus报文经常出现0x00这个字节(比如我控制灯灭的指令01 06 00 00 00 00 ...,里面全是0x00);

  • 如果用strcpy:单片机刚把第 3 个字节0x00读进去,strcpy立刻以为“到结尾了”,直接停止搬运。剩下的 5 个字节全部丢弃!随后,你的程序去解析这包缺失了一半的报文,就会访问数组越界,瞬间触发你之前遇到的HardFault死机

  • 如果用memcpy:不管中间有多少个0x00,它都会老老实实把 8 个字节完整搬完,然后我可以去完整校验 CRC、解析功能码;

memset:内存设置(memory set);将内存区域内的每一个字节,全部填充我指定的一个数值。

例如我写的memset(xUSART1.receiveData, 0, sizeof(xUSART1.receiveData));//意思是从我的xUSART1.receiveData这个数组的首地址开始设置,后面的xUSART1.receiveData大小的字节元素都置为0

void *memset(void *s, int c, size_t n);

void *s

  • 是什么:这是一个通用无类型指针,代表你要开始填充的“内存目标首地址”。

int c

  • 是什么:你要填充的数值。

  • size_t n

  • 是什么:要填充的总字节数。size_t在 STM32 编译器中通常是一个无符号 32 位整数。

返回列表