c语言容易忽视的问题
关键字
static
作用:改变作用域,改变生存期
修饰函数,全局变量,局部变量。值得注意的是:
修饰局部变量时,由于被 static 修饰的变量总是存在内存的静态区,所以即使这个函数运行结束,这个静态
变量的值还是不会被销毁,函数下次使用时仍然能用到这个值。*局部变量不会随着函数结束而结束,但是只
有在函数里面才可以访问到这个变量,这就是和修饰全局变量的区别。*static int j; void fun1(void) { static int i = 0; i ++; printf("i :%d\n",i); } void fun2(void) { j = 0; j++; printf("j :%d\n",j); } int main() { for(int k=0; k<10; k++) { fun1(); fun2(); } return 0; }
以上的结果是i的值会一直增加,并且只会被初始化一次。
static 与 extern 是不兼容的。使用static修饰的变量没有办法使用extern开放出去。
const
const什么时候和volatile同时使用:
可能发生在该变量是只读寄存器,度与
volatile 使用的地方:
并行设备的硬件寄存器(如:状态寄存器)
一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
多线程应用中被几个任务共享的变量
union
位操作:
清零赋值
a &=~ b 等价为 a = a & (~b)
常用 :&=~ 清零,|= 置位,& 查看
例0x1234,共16位,每个数字4位,共2个bit(字节),每个bit有8位假设从0位开始
查看x位 reg & (0x1 << x)
将某x位置1 reg |= (0x1 << x)
将某x位置0 reg &=~ (0x1 << x)
查看3到7位 reg & (0x1f << 3)
将3到7位置0 reg &=~ (0x1f << 3)
将3到7位置x reg &=~ (0x1f << 3) ; reg |= (x << 3) 先清零后置位除法向上取整
(x+y-1)/y对其
4对其:(x >> 2 << 2)
或者是:x += (4-1) ; x &=~ 4arrribtte的使用
对于struct结构体在编译过程中会自动优化对其的。默认4字节对其的。
手动改变结构体成员的顺序可以改变结构体的大小,式结构变得紧凑。
使用__attribute__((packed))
取消对其,或者使用__attribute__((alligned(n)))
指定n个字节对其。
预处理
- 多语句宏的书写方式
多语句宏的书写使用do while(0),这样不仅是为了增加代码的可读性更是实际的需要.
如果不使用的话
#define DOSOMETHING()\
foo1();\
foo2();
这样的话会将后面的两个函数宏替换
if(a>0)
DOSOMETHING();
展开替换为
if(a>0)
foo1();
foo2();
这样的话不论怎样都会执行第二个函数,因此不加肯定是有问题的。
如果使用{}
#define DOSOMETHING()\
{\
foo1();\
foo2();\
}
展开之后就是
if(a>0)
{
foo1();
foo2();
};
这样就会有语法错误
因此这样使用
#define DOSOMETHING()\
do{\
foo1();\
foo2();\
}while(0)
这样的话宏替换就不会出现问题了。
使用宏函数的优点和缺点:
优点:提高执行效率,没有调用函数的开销。
缺点:大量使用宏函数会增加代码的体积,执行的文件变大。- 头文件的相互调用
为了避免出现头文件相互调用的时候出现重复定义,使用以下的技巧
- 头文件的相互调用
#ifndef HFILENAME_USED
#define HFILENAME_USED
... 头文件内容 ...
#endif
指针和数组
数组指针变量
定义一个指针数组变量
char (*buff)[10];
//当成
char (*)[10] buffer
,就好理解了,但是不能这么写数组名a作为左值和右值的区别
左值:修改变量的值
右值:读取变量的值
数组名只能作为右值,不能作为左值,作为右值的时候表示&a[0]
(数组元素的首地址),而不是&a
(数组的首
地址)int a[100]; int main() { printf("&a[1]:%x,a+1:%x, &a+1:%x\n", &a[1], a+1, &a+1); return 0; }
结果:&a[1]:601064,a+1:601064, &a+1:6011f0
也可以总结为:数组名只有在使用sizeof的时候代表数组,其他的时候都只是数组元素的首地址.
因此当数组作为函数的行参数就没办法使用sizeof知道数组的大小,必须另外传进来。
大端模式:高字节放在低地址中,低字节放在高地址中。
小段模式:低字节放在高地址中,高字节放在低地址中。二维数组作为函数参数传递
数组蜕为指针的规则是不能递归使用,也就是说二位数组的传递不能使用指针的指针
行参必须使用二位数组或者是数组指针。因为如果不是这样的话就没有办法,确定每一维数组的长度了。柔性数组
技巧就是,在结构体里面使用长度为0的数组,结构体分配内存大小时算上要分配的数组的大小,这样的话
可以使用这个数组了,并且数组的内存就在结构体后面。释放的时候只用释放结构体的内存就可以一起释放了。
主要的优点是,数组的指针和内存是连续的。方便使用管理。
typedef struct a {
int size;
int array[0];
}A;
A *a;
a = (A*)malloc(sizeof(A)+sizeof(int)*10);
结构体
结构体之间的直接赋值
_同种类型的结构体之间可以直接赋值,如果结构体里面有指针变量,那么两个结构体里的指针指向的是同一
个块内存,并没有重新分配一块内存。_结构体初始化的三种方式
typedef struct a {
int a;
int (*fun1)(int a);
int (*fun2)(char b);
int b;
}A;
int fun1(int a)
{
printf("1\n");
return 0;
}
int fun2(char b)
{
printf("2\n");
return 0;
}
int main()
{
A a1 = {
.a = 1,
.fun1 = fun1,
.fun2 = fun2,//通常使用这种方式
};
A a2 = {
a : 2,
fun1 : fun1,
fun2 : fun2,
};
A a3 = {
1,
fun1,
fun2,
};
}
表达式
- ?:
这个三目运算符,返回值是一个右值,是不能被赋值的,另外类型强制转换之后也是右值。
int a,b,c;
(a==0?b:c) = 10;//这样写是错误的
*(a==0?&b:&c) = 10;//这样写就可以了
函数
函数指针的类型
定义一个函数指针变量 int(*Fun)(char *a);
typedef int(*Fun)(char *a);//这里Fun不是函数指针,指的是函数指针变量。 (*fun(0x0))(x);//或者是(*(int(*)(char *)0x0))(x);
函数当作另一个行参(主要用作回调函数)
typedef int(*Fun)(int a);
int fun1(int a)
{
a++;
printf("a:%d\n", a);
return 0;
}
void fun2(int b, int fun(int a))
{
fun(b);
}
void fun3(int b, int (*fun)(int a))
{
fun(b);
}
void fun4(int b, Fun fun)
{
fun(b);
}
int main()
{
fun2(1,fun1);
fun3(2,fun1);
fun4(3,fun1);
return 0;
}
> 结果2,3,4
函数指针数组
定义一个指针数组变量
int(*Fun[10])(char *a);
//当成int(*)(char*a) Fun[10];
就很好理解了,但是不能这么写。函数参数传递
只有参数是数组的时候是双向值传递,其他的时候都是单向值传递
当传进函数的参数只用来读的时候,传值就可以了。当传进来的参数要写的时候就要传地址,(相对于该变
量来说,也可以说是双向值传递)
这个完全适用于指针变量。当我们传递内存的时候传进来指针变量。因为我们要改变这个指针变量的值,
因此要传递它的指针,也就是二级指针。或者使用返回值的方式。可变参数函数
标准库函数
itoa, sprintf, strtok
文件操作函数
内存使用
内存池
为了避免出现内存碎片,将内存统一管理。提前将内存malloc成很多不同大小的块。每次分配的时候使用提前分
配好的固定大小的内存。内存越界检查
一般在内存的结尾加上固定长度的字符串,程序运行时检查该字符串有没有被别人冲掉