C语言陷阱与技巧第40节,现在也能使用变量定义“变长数组”了,为什么要尽量减少malloc()?

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() 是昂贵的,所以应该尽量减少使用它。

阅读更多:   杂谈 , C语言
添加新评论

icon_redface.gificon_idea.gificon_cool.gif2016kuk.gificon_mrgreen.gif2016shuai.gif2016tp.gif2016db.gif2016ch.gificon_razz.gif2016zj.gificon_sad.gificon_cry.gif2016zhh.gificon_question.gif2016jk.gif2016bs.gificon_lol.gif2016qiao.gificon_surprised.gif2016fendou.gif2016ll.gif