C语言关键字讲解及其拷贝库函数、指针常量与常量指针的区别

发布于:2023-01-10 ⋅ 阅读:(136) ⋅ 点赞:(0)

在学习C语言的课程的过程中,曾遇到过许多不理解的问题,通过网上查阅资料以及询问他人,对于一些常用的关键字的用法与理解,有了自己的一点认识,拷贝函数、指针常量、常量指针也有了一点见解,如有错误,望批评指正。

4G虚拟内存图

4G虚拟内存

static关键字

1static 修饰局部量
	局部变量的生命周期得到延长。局部变量默认是程序运行的时候,在栈中分配的空间,这样在函数结束的时候,局部变量对应的内存已经被释放掉了。当用 static 修饰局部变量的时候, static 修饰的局部变量在分配的空间在静态区区域(data/bss 段),这样的局部变量,不会在函数调用结束的时把对应的空间释放掉。
	局部变量值的得以保留,不论函数调用多少次局部变量只初始化一次。局部变量如果没有赋初值的时候,默认值为随机值(程序运行的时候,在栈中开辟空间,此时分配内存中存放的值无法确定)。 static 修饰的局部变量默认值为 0,并且这个变量的值具有继承性(每次调用的时候, static 修饰的变量不会每次重新赋值,而是接着使用上一次的值)2static 修饰函数/全局变量 
    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 后查看

网站公告

今日签到

点亮在社区的每一天
去签到