C语言关键字、函数杂谈(1)

1、static关键字

static关键的,是用来声明静态变量的。主要作用有两个:

  • 隐藏与隔离的作用(当同时编译多个文件)
  • 保持变量内容的持久性

下面就这两个功能进行详解:

(1)隐藏与隔离的作用(当同时编译多个文件):

​  上面已经阐述过,全局变量虽然属于静态存储方式,但并不是静态变量。全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,全局变量在各个源文件中都是有效的。

​​  如果我们希望全局变量仅限于在本源文件中使用,在其他源文件中不能引用,也就是说限制其作用域只在定义该变量的源文件内有效,而在同一源程序的其他源文件中不能使用。这时,就可以通过在全局变量之前加上关键字 static 来实现,使全局变量被定义成为一个静态全局变量。这样就可以避免在其他源文件中引起的错误。也就起到了对其他源文件进行隐藏与隔离错误的作用,有利于模块化程序设计。

a.c

#include <stdio.h>
char a = 'A'; // global variable
void msg()
{
    printf("Hello\n");
}

main.c

#include <stdio.h>
int main(void)
{    
    extern char a;    // extern variable must be declared before use
    printf("%c ", a);
    (void)msg();
    return 0;
}

程序运行结果是:A Hello

(2)保持变量内容的持久性:

​​  有时候,我们希望函数中局部变量的值在函数调用结束之后不会消失,而仍然保留其原值。即它所占用的存储单元不释放,在下一次调用该函数时,其局部变量的值仍然存在,也就是上一次函数调用结束时的值。这时候,我们就应该将该局部变量用关键字 static 声明为“静态局部变量”。

​​  当将局部变量声明为静态局部变量的时候,也就改变了局部变量的存储位置,即从原来的栈中存放改为静态存储区存放。这让它看起来很像全局变量,其实静态局部变量与全局变量的主要区别就在于可见性,静态局部变量只在其被声明的代码块中是可见的

​​​  对某些必须在调用之间保持局部变量的值的子程序而言,静态局部变量是特别重要的。如果没有静态局部变量,则必须在这类函数中使用全局变量,由此也就打开了引入副作用的大门。使用静态局部变量最好的示例就是实现统计次数的功能,如下面示例所示。



2、extern关键字

​ ​ 在C语言中,修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。extern声明不是定义,即不分配存储空间。另外,extern关键字只需要指明类型和变量名就行了,不能再重新赋值,初始化需要在原文件所在处进行,如果不进行初始化的话,全局变量会被编译器自动初始化为0。像下面这句话是错的:

extern int num=4;

extern用法:

1、引用同一个文件中的变量。

2、引用另一个文件中的变量

main.c

#include<stdio.h>

int main()
{
    extern int num;
    printf("%d",num);
    return 0;
}

b.c

#include<stdio.h>
int num = 5;
void func()
{
    printf("fun in a.c");
}

程序运行结果是:num=5

​​ ​ 使用include将另一个文件全部包含进去可以引用另一个文件中的变量,但是这样做的结果就是,被包含的文件中的所有的变量和方法都可以被这个文件使用,这样就变得不安全,如果只是希望一个文件使用另一个文件中的某个变量还是使用extern关键字更好。



3、auto关键字

auto 是C++程序设计语言1的关键字。用于两种情况
(1)声明变量是根据初始化表达式自动推断该变量的类型
(2)声明函数时函数返回值的占位符



4、register关键字

4.1 register的使用

​ ​ register关键字,暗示编译程序相应的变量将被频繁地使用,如果可能的话,应将其保存在CPU的寄存器中,以加快其存储速度。
什么情况用寄存器变量:

​ ​ 当对一个变量频繁被读写时,需要反复访问内存,从而花费大量的存取时间。为此,C语言提供了一种变量,即寄存器变量。这种变量存放在CPU的寄存器中,使用时,不需要访问内存,而直接从寄存器中读写,从而提高效率。寄存器变量的说明符是register。对于循环次数较多的循环控制变量及循环体内反复使用的变量均可定义为寄存器变量,而循环计数是应用寄存器变量的最好候选者。

4.2 register关键字的注意点
  • register变量必须是能被CPU寄存器所接收的类型,意味着register变量必须是一个单个的值,并且其长度应该小于等于寄存器长度。
  • register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。
  • 只有局部变量和形参才可以定义为寄存器变量。因为寄存器变量属于动态存储方式,凡需要采用静态存储方式的量都不能定义为寄存器变量,包括:模块间全局变量、模块内全局变量、局部static变量
  • 早期的C编译程序不会把变量保存在寄存器中,除非你命令它这样做,这时register修饰符是C语言的一种很有价值的补充。然而,随着编译程序设计技术的进步,在决定哪些变量应该被存到寄存器中时,现在的C编译环境能比程序员做出更好的决定。实际上,许多编译程序都会忽略register修饰符,因为尽管它完全合法,但它仅仅是暗示而不是命令。


5、volatile关键字

​ ​ volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。



6、realloc()函数

​ 这个函数不建议使用,有很多坑点。

​ 原型:extern void *realloc(void *mem_address, unsigned int newsize);

功能:

先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。

问题:

​ realloc如果重新开辟了空间,原指针内存会被释放吗?还需要再调用free释放原指针吗?

​ Linux环境下:realloc重新申请地址块成功后会释放掉原地址块,即不用再调用free释放原始指针(否则会报错double free)。

​ Windows环境下:realloc重新申请地址块成功后不会释放掉原地址块,为了可移植性,可以不用free原指针。

realloc()函数使用可能会出现的bug:

1)realloc() 第一种行为引发的Bug

void *ptr = realloc(ptr, new_size);
if (!ptr) {
    // 错误处理
}

  这里就引出了一个内存泄露的问题,当realloc() 分配失败的时候,会返回NULL。但是参数中的 ptr 的内存是没有被释放的。如果直接将realloc()的返回值赋给ptr。那么当申请内存失败时,就会造成ptr原来指向的内存丢失,造成内存游离和泄露。

正确的处理应该是这样:

void *new_ptr = realloc(ptr, new_size);
if (!new_ptr) {
    // 错误处理。
}
ptr = new_ptr

2)第而种行为引发的Bug 实际上,malloc(0)是合法的语句,会返还一个合法的指针,且该指针可以通过free去释放。这就造成了很多人对realloc()的错误理解,认为当size为0时,实际上realloc()也会返回一个合法的指针,后面依然需要使用free去释放该内存。
void *new_ptr = realloc(old_ptr, new_size);
//其它代码
free(new_ptr);

  由于错误的认识,不去检验new_size是否为0,还是按照new_size不为0的逻辑处理,最后并free(new_ptr)。这里就引入了double free的问题,造成程序崩溃。

  所以,realloc() 这个设计并不怎么优良的函数陷阱还是不少的,一不小心就踩雷了,上面只是两个简单的小例子,大家在实际使用的时候还应该注意一些其他小问题。



4、sizeof()和strlen()
char *c="abcdef";

char d[]="abcdef";

char e[]={'a','b','c','d','e','f'};

 
printf("%d%d\n",sizeof(c),strlen(c));  //4   6

printf("%d%d\n",sizeof(d),strlen(d));  //7   6

printf("%d%d\n",sizeof(e),strlen(e));  //6   19(vs编译出来19,dev-c编译出来6)
//出错的原因就是strlen()函数,e不是字符串,最后不以'\0'结尾

char e[] = { 'a','b','c','d','e','f','\0' };
printf("%d%d\n",sizeof(e),strlen(e));  	//7   6

输出的结果是:

4 6

7 6

6 19

7 6

sizeof计算的是分配空间的实际字节数,包括 ‘\0’

但strlen是计算的空间中字符的个数(不包括**‘\0’**)

  • sizeof是运算符,可以以类型、函数、做参数 。strlen是函数,只能以char*(字符串)做参数。而且,要想得到的结果正确必须包含 ‘\0’(通过strlen的实现得知)。
  • sizeof是在编译的时候就将结果计算出来了是类型所占空间的字节数,所以以数组名做参数时计算的是整个数组的大小。而strlen是在运行的时候才开始计算结果,这是计算的结果不再是类型所占内存的大小,数组名就退化为指针了。
  • 另外,sizeof不能计算动态分配空间的大小,只会当成指针大小来看(4个字节)。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值