C语言常用容易出错的用法
版权声明:本文为 neucrack 的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接(持续更新):https://neucrack.com/p/61
结构体初始化typedefstruct
{
uint8_tsck;
uint8_tmiso;
uint8_tmosi;
uint8_tcs;
uint8_tcpol;
uint8_tcpha;
}spi_simulate_config_typedef;
spi_simulate_config_typedef spi={
.cpol=0,
.cpha=0
}
spi_simulate_config_typedef spi2={
cpol:0,
cpha:0
}
结构体最后一个成员的用法typedefstruct{
inta;
floatb;
int*c;
}a_t;typedefstruct{
inta;
floatb;
intc[];
}a_t;typedefstruct{
inta;
floatb;
intc[0];
}a_t;
三者等效,可以使用如下方式来分配空间
a_t*p=malloc(sizeof(a_t)+sizeof(int)*8);
另外需要注意,如果最后一个元素定义是如int c[]的形式,则下面的代码无法编译通过
typedefstruct{
inta;
intb[];
}a_t;
intmain()
{
a_tp;
intc[5];
p.b=c;
return0;
}
使用int* c则可以
typedefstruct{
inta;
int*b;
}a_t;
intmain()
{
a_tp;
intc[5];
p.b=c;
return0;
}
数组的初始化方式#include"stdio.h"
enum{ENUM1=0,ENUM2,ENUM3};
intmain(){
chararray0[3]={1,2,3};
chararray1[3]={
[0]=1,
[1]=2,
[2]=3
};
chararray2[3]={
[ENUM1]=1,
[ENUM2]=2,
[ENUM3]=3
};
for(inti=0;i<3;++i)
printf("%d\n",array0[i]);
for(inti=0;i<3;++i)
printf("%d\n",array1[i]);
for(inti=0;i<3;++i)
printf("%d\n",array2[i]);
return0;
}
另外,你可能会遇到一种奇怪的写法,不建议使用:
chararray2[4]={
#defineNUMBER0(10)
ENUM1,
ENUM2,
ENUM3
};
可控储存的结构体,占位符的使用typedefstruct
{
enum
{
TEST_FRAME_VER_0=0,
TEST_FRAME_VER_MAX,
}version:8;
uint8_tgateway:1;
uint8_tfragment:1;
enum
{
TEST_FRAME_TYPE_DISCOVER=0,
TEST_FRAME_TYPE_LINK=1,
TEST_FRAME_TYPE_DATA=2,
TEST_FRAME_TYPE_MAX,
}type:2;
enum
{
TEST_FRAME_SUBTYPE_DISCOVER_REQ=0,
TEST_FRAME_SUBTYPE_DISCOVER_RESP=1,
TEST_FRAME_SUBTYPE_DISCOVER_MAX
}subtype:4;
uint8_tresv:8;
uint8_tdest_length_type:4;
uint8_tsrc_length_type:4;
void*raw;
}__attribute__((aligned(1),packed))test_frame_t;
uint8_tframe[4];
test_frame_ttest_frame;
memcpy(frame,test_frame,4);
C99中数组定义
C99开始可以使用变量来定义数组长度
eg:
intnum=10;
intarray[num];
但是也要注意,需要局部变量时使用,空间实在栈上分配的。
在 c99 之前,可以在函数内使用 alloca函数来动态从栈上获取不定长度的空间
keil MDK5也可以使用这个哦,不过在编译选项里面加一个—gnu参数就可以了
强制转换问题
我们常常使用各种指针指过去指过来、强制转换过去转换过来、飞过去飞过来~~~然后就飞不见了!!!!!
案例1:强制转换导致越界访问,这种情况下如果编译器检查不严格很难发现,如果检查严格,会出现非法访问地址错误
#include"stdio.h"
#include"stdint.h"
intmain()
{
uint8_ta=10;
void*p=(void*)&a;
uint16_tb=(uint16_t)(*((uint8_t*)p));
uint16_tc=*((uint16_t*)&p);
printf("a=%d,b=%d,c=%d\n",a,b,c);
}正确的是b的写法,c是错误的
一种函数的神奇写法intplus(a,b)
inta;
intb;
{
returna+b;
}
在一些老版程序中会出现,现在不建议这样用了,但是有可能会遇到别人这样写!!
内存越界访问问题
比如
#include
#include
#include
intmain()
{
uint64_ta=1024;
uint16_tb;
uint32_t*c=&b;
*c=a;
printf("%d %d %d\r\n",sizeof(a),sizeof(b),sizeof(*c));
return0;
}
这里将一个使用了强制转换,在编译的时候会报警告,如果不理会强制运行则会导致内存越界访问,程序崩溃,当然,在PC上可能会优化而不会崩溃,但是在一些单片机中就不会这么好了
编译优化以及 volatile
在某些情况下,代码有可能会被优化,比如:
boolflag=false;
voidon_interrupt()
{
flag=true;
}
main()
{
flag=false;
exec_with_interrupt(on_interrupt);
while(!flag){}
printf("end");
}
这里exec_with_interrupt执行过程中会调用参数传入的回调函数,回调函数里面会将标记置位真,但在某些情况下(不一定会出现,但可能出现),编译器优化会认为flag需要优化,于是死循环变成了while(false){},最终导致永远在死循环内不会出来!
可能又会发现,在死循环里面加一个打印函数或者改一下编译优化等级程序又正常了!
这种情况下告诉编译器不要优化flag就好了,使用volatile修饰flag即可
volatileboolflag=false;
变量分配的地址对齐(aligned)
有时为了避免出现一些奇怪的错误, 或者某些特定的语句或者硬件需要,我们需要指定某些变量申请的地址在内存中对齐(比如有些 DMA 要求操作128字节对齐)
比如
inta;
可能分配到的地址是0x800127b8, 如果我们改成
inta __attribute__((aligned(64)));
则分配到的地址就会是0x80012780
比如我们用强制转换的时候就会十分在意地址,如果不是强制转换成的类型的长度的整数倍,就会导致程序崩溃, 如;
uint8_ta[10];
// 比如这里a[0]的地址是8的整数倍
uint64_ttemp=*((uint64_t*)a);
temp=*((uint64_t*)(a+1));//这里可能会崩溃
这里有可能会崩溃,也有可能不会,不会是因为编译器做了处理
结构体的对齐(aligned) 和 紧凑型(packed)
类似, 如果__attribute__((align(64))) 用到结构体声明上,就是指明结构体需要多少字节对齐,比如64字节对齐,比如:
typedefstruct
{
boola;
}aaa_t;
就算这个结构体的大小只有一个字节,最后一个变量也会占用64字节
而packed就是相反
typedefstruct
{
boola;
}aaa_t__attribute__((packed));
用这个结构体定义的变量只会占用一个字节, 如果不熟练,尽量少用这种方式,这种方式一般用在定义通信协议的时候
宏定义妙用
: 取字符,比如#define A(x) #x, 调用A(aaa)得到字符串“aaa”
: 连接,比如#defineA(x)a##x
inta10=100;
intmain()
{
printf("%d\n",A(10));
}
最后输出100,注意这里传入的10只能是在编译前能够确定值的数,比如这里的常数10,不能传入变量,比如下面这个就不行
#defineA(x)a##x
inta10=100;
intmain()
{
intb=10;
printf("%d\n",A(b));
}
因为宏定义展开是在预处理阶段,是不能得到变量具体的值的,上面A(b)只能得到ab,而且不是字符串,是变量ab,然而并不存在这个变量,所以编译阶段就会报错
单引号字符串uint32_tdata=0x41424344;// D在内存低字节, A在高字节
intret2=(data=='ABCD');
此处, ret2 的值为 1!!
虽然编译会报警告