在学习C语言的课程的过程中,曾遇到过许多不理解的问题,通过网上查阅资料以及询问他人,对于一些常用的关键字的用法与理解,有了自己的一点认识,拷贝函数、指针常量、常量指针也有了一点见解,如有错误,望批评指正。
4G虚拟内存图
static关键字
1)static 修饰局部量
局部变量的生命周期得到延长。局部变量默认是程序运行的时候,在栈中分配的空间,这样在函数结束的时候,局部变量对应的内存已经被释放掉了。当用 static 修饰局部变量的时候, static 修饰的局部变量在分配的空间在静态区区域(data/bss 段),这样的局部变量,不会在函数调用结束的时把对应的空间释放掉。
局部变量值的得以保留,不论函数调用多少次局部变量只初始化一次。局部变量如果没有赋初值的时候,默认值为随机值(程序运行的时候,在栈中开辟空间,此时分配内存中存放的值无法确定)。 static 修饰的局部变量默认值为 0,并且这个变量的值具有继承性(每次调用的时候, static 修饰的变量不会每次重新赋值,而是接着使用上一次的值)。
2)static 修饰函数/全局变量
static 修饰的函数/全局变量的作用范围被限定在本文件中。默认情况下函数名或全局变量是全局可见的,在头文件中经常使用关键字 extern 声明。但当用 static 修饰它们的时候,暗示该函数/变量只是在本文件中有效,就不能再在头文件中用 extern 关键声明。
const关键字
const 修饰的数据类型是指常类型,常类型的变量或对象的值是不能被更新的。
编译器通常不为普通 const 常量分配存储空间,而是将它们保存在符号表中。const 定义常量从汇编 的角度来看,只是给出了对应的内存
地址,而不是像#define 一样给出的是立即数,所以 const 定义的常 量在程序运行过程中只有一份拷贝,而#define 定义的常量在内存中
有若干个拷贝。这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
const 关键字的作用主要有以下几点:
1)可以定义 const 常量,使其具有不可变性。eg:
const int len = 100;
int Array[len]; //允许用常量初始化数组
int len = 100;
int Array[len]; //不允许用变量初始化数组
2)便于进行参数类型检查,消除了一些隐患。eg:
void func(const int i)
{
... //形参 i是一个常量,不允许被修改
}
void func(const int i)
{
i = 10; //错误,i不允许被修改
}
3)const 修饰普通变量,eg:
const int a; //正确!
int const a; //正确!
int a const; //错误! 语法错误
a = 5; //错误! 不能改a
4)const 修饰指针变量,eg:
int num;
const int *a; //常量指针写法,指向常量的指针
int const *a;
const int const *a;
a = # //正确! 修改指针指向
*a = 20; //错误! 不能间接寻址
int * const b = # //指针常量写法,指针同时是一个常量
b = NULL; //错误! 不能修改指针指向
*b = 20; //正确! 间接寻址
const int * const c = # //常用写法
int const * const c = #
const int const * const c = #
c = NULL; //错误! 不能修改指针指向
*c = 20; //错误! 不能间接寻址
5)可以节省空间,避免不必要的内存分配。eg:
#define PI 3.14159 //常量宏
const doulbe pi = 3.14159; //此时并未将pi放入 ROM 中
double i = pi; //此时为 pi 分配内存,以后不再分配!
double j = pi; //pi 没有内存分配
double I = PI; //编译期间进行宏替换,分配内存
double J = PI; //编译期间进行宏替换,又一次分配内存!
volatile关键字
Volatile 中文含义表示”易变的”,它是 C 语言中用来修饰变量的一个关键字。
需要知道的是:每个编译器都有自己的优化级别。编译器进行编译成机器码时,如果它发现这个变量已经在前面的代码执行过程中(已将变量加
载到寄存器),那么后面在使用这个变量的时候,就不会再从内存中重新读值,而是直接使用寄存器中的值。这样做目的是提高程序运行的效率,但同时也会带来一个很隐晦的bug。我们来看看如下代码:
short flag;
__interrupt handler()
{
flag = 1;
}
void test()
{
do1();
while(flag == 0);
do2();
}
//代码解读:
这段程序等待内存变量 flag 的值变为 1 之后才运行 do2()。变量 flag 的值由别的程序更改,这个程序可能是某个硬件中断服务程序。例如:如果某个按钮按下的话,在按键中断程序中修改 flag 为 1,这样上面的程序就能够得以继续运行。但是,编译器并不知道 flag 的值会被别的程序修改,因此在它进行优化的时候,可能会把 flag 的值先读入某个寄存器,然后等待那个寄存器变为 1。如果不幸进行了这样的优化,那么 while 循环就变成了死循环,因为寄存器的内容不可能被中断服务程序修改。为了让程序每次都读取真正 flag 变量的值,就需要定义为如下形式:
volatile short flag;
需要注意的是,没有 volatile 也可能能正常运行,但是可能修改了编译器的优化级别之后就又不能正常运行了。因此经常会有debug 版本正常,但是 release 版本却不正常的问题。所以为了安全起见,只要是等待别的程序修改某个变量的话,就加上 volatile 关键字。
总结::volatile 关键字的主要用途是为了防止编译器优化,告诉编译器,在使用它修饰的变量时,必须每次从内存中重新读值,而不是直接使用上一次从内存中加载到寄存器中的值。
库函数 strcpy()和 memcpy()的区别
char *strcpy(char *dest, const char *src); | void *memcpy(void *dest, const void *src, size_t n); | |
---|---|---|
操作对象 | 只能是字符串数组 | 任意类型的数据 |
功能特性 | 将指针 src 指向的数据依次拷贝到 dest 指向的单元,直至’\0’结束; | 将指针 src 指向的数据依次拷贝到 dest 指向的单元,拷贝 n 个字节数据; |
注意事项 | 1)src 指向的字符串不能长于 dest 指 向的空间,不然会覆盖其他数据; 2)src 指向的数据块必须有’\0’结尾, 不然会数组越界; | 1)src 指向数据不得长于 dest 指向的空间,不 然会覆盖其他数据; 2)n 不得大于 src 直线的数据块长度,不然会 数组越界; |
指针常量与常量指针的定义及区别
指针常量 | 常量指针 | |
---|---|---|
定义 | 指针所指向的位置不能改变,即指针本身是一个常量。 | 指向常量的指针,简称常量指针。 |
用法举例 | int * const p = &a; | const int * p; int const * p; |
注意事项 | 1)指针常量必须在声明的同时对其初始化,不允许先声明一个指针常量随后再对其赋值; 2)它是个常量!指针所指向不可以变化,但是指向的地址所对应的内容可以变化; | 1)可以将一个变量的地址赋值给常量指针,防止其修改内存数据; 2)指针还可以指向别处,因为指针本身只是个 变量,可以指向任意常量的地址; |
本文含有隐藏内容,请 开通VIP 后查看