C语言近些年也是有一定的发展的,例如C99标准增加了C语言的新特性——“变长数组”,这允许程序员使用非常数定义数组,使得C语言程序开发在某种程度上方便了不少。
C语言中的“变长数组”
遗憾的是,国内相当一部分C语言教材比较旧,在介绍如何定义C语言数组时,一般都会强调数组的长度必须使用常数表示,使用变量作为长度定义数组是非法的:
int arr[16];
#define N 32
int arr_2[N];
int n = 64;
int arr_3[n]; // 非法
然而,如今大多数平台其实已经支持C语言的“变长数组”了,也就是说上面的第三种定义方式其实是合法的,为了验证这一点,编写下面的C语言程序,请看:
#include <stdio.h>
void set_arr(int n)
{
char arr[n];
printf("len of arr: %d\n", sizeof(arr)/sizeof(char));
}
int main()
{
set_arr(16);
set_arr(32);
return 0;
}
编译并执行这段C语言代码,可以得到如下输出:
# gcc t.c
# ./a.out
len of arr: 16
len of arr: 32
可见,我的C语言编译器是支持“变长数组”的。不过需要注意的是,变长数组不能在定义的时候被初始化,例如下面这样定义就非法了:
int n = 32;
char arr[n] = {0}; // 非法
malloc() 的取舍
不知道读者如何,反正我当初刚接触C语言时,要申请一块内存时,我认为只有在不能事先确定所需内存长度的时候,才需要使用 malloc(),其他时候直接使用数组省事。现在C语言支持变长数组了,是不是 malloc() 就没用了呢?
当然不是的。要知道,在C语言函数内部定义的变长数组仍然在本函数栈帧内,它仍然属于局部变量。局部变量的生命周期只维持在本函数内,很难将其传递到函数外部使用。另外,栈空间是有限的,如果C语言程序所需内存长度较长,使用局部数组申请内存很容易导致栈溢出。
另外,并不是所有的平台都支持C语言的新特性的。
这么看来,malloc() 是C语言程序开发中不可缺少的单元。但是应该注意,使用 malloc() 申请内存使用,管理起来要比使用作为局部变量的“变长数组”昂贵多了。
使用 malloc() 最大的问题就是要确保和 free() 配对,否则就会造成C语言程序内存泄漏。在简单程序中,我们当然不会忘记“配对”,但是如果某个函数具有多个分支,只要忽略一个分支,就有可能造成内存泄漏。
要是这种“内存泄漏”隐藏在一个巨大的C语言项目中,要排查它常常比较难,因为它只在某个条件达成时,才会触发,所以你甚至不能保证每次都能重现它。
另外,malloc() 是有可能失败的,所以在C语言程序开发中使用 malloc() 时,常常还需要判断是否分配成功,一次较为完整的使用是下面这样的,请看:
int n = 16;
char *buf = (char*)malloc(n);
if(!buf){
// 错误处理
}
...
free(buf);
可见,使用 malloc() 分配内存使用对于程序员也是不友好的,为了申请 16 字节的内存,程序员需要写额外的多行C语言代码。如果上述代码发生在某个函数中,使用变长数组的话,其实只需要一行代码就可以了,下面这行C语言代码和上述代码是等价的:
char buf[n];
使用 malloc() 的“昂贵”还不止于此,malloc() 从堆中申请内存并不是逐字节申请的,在有的平台上,malloc() 实际申请的内存是 32 Bytes 的整数倍。也就是说,上面我们实际上只需要 16 字节内存,但是 malloc() 却占用了 32 字节的内存,这显然是一种空间浪费。
因此,很多经验丰富的C语言程序员都尽可能的避免在自己的项目中使用 malloc(),只在不可避免的情况下才使用它。
数组与 malloc() 结合的小技巧
经过上面的讨论,相信读者应该发现在C语言开发中 malloc() 是必不可少的,但是我们又要尽可能的避免使用它,在了解了文章开头介绍的C语言“变长数组”这一新特性之后,其实很多情况都能做到这一点。
不过,我们也已经知道,如今并不是所有平台都已支持C语言的“变长数组”特性,所以要是C语言程序可能需要移植到这些平台上,“变长数组”也是不推荐使用的。但是我们仍然可以通过一些小技巧,减少 malloc() 的使用,例如下面个例子:
char *bufp;
char buf[1024];
if (need_sz >= 1024)
bufp = (char *)malloc(need_sz);
else
bufp = buf;
....
if (bufp != buf) free(bufp);
从上述C语言代码可以看出,程序实际需要使用的内存通过 bufp 指针管理,当所需内存长度小于我们实现设定的 1024(该值可以按需修改)时,bufp 使用数组。当所需长度大于 1024 时,程序才会调用 malloc() 申请内存供 bufp 管理使用.
小结
本文介绍了C99中新增的“变长数组”C语言新特性的用法,并给出了使用示例。重点讨论了C语言程序开发中 malloc() 的使用取舍,鉴于使用 malloc() 是昂贵的,所以应该尽量减少使用它。