C语言基本功修炼秘籍第6节,指针和内存的关系

指针是C语言的灵魂,因此随意挑选一个C语言程序项目,一般都能够看到指针的大量应用。指针允许程序员访问内存,但是初学者往往会误以为“指针就是内存”,写出错误的C语言代码,为此,本节将以问答的方式讨论C语言中指针与内存的关系。

下面这段C语言代码为什么不能正常工作?

小明写了一段C语言程序,允许用户输入一段字符串,并将其打印到控制台,核心代码段如下:

char *answer;
printf("Type something:\n");
gets(answer);
printf("You typed \"%s\"\n", answer);

小明定义了一个 char 型指针,用于接收用户输入的字符串,但是编译执行这段C语言代码时,它发现程序并不能按照他的预期进行,这是为什么呢?

这是因为指针 answer 尚未指向系统分配给该程序使用的内存。事实上,上述C语言代码中的 answer 没有被初始化,它就有可能指向任意地址,而它指向的内存并不是提供给这段程序使用的。所以,小明的C语言程序表现就未知了,可能根本无法编译通过,可能表现正常,也有可能运行时崩溃。

归根结底,就是 answer 指针没有指向一块属于自己的内存。若希望小明的C语言程序正常,可以将 answer 改为数组,例如:

#include <stdio.h>
#include <string.h>

char answer[100], *p;
printf("Type something:\n");
fgets(answer, sizeof(answer), stdin);
if((p = strchr(answer, '\n')) != NULL)
    *p = '\0';
printf("You typed \"%s\"\n", answer)


修改后的C语言代码还将 gets() 改为 fgets(),以确保用户的输入不会溢出数组 answer。

既如此,下面这段C语言代码为什么又可以正常工作了呢?

可能有读者曾经写过下面这样的C语言代码,按照上面的分析,代码并没有让 p 指向设定的内存,为什么还是能够得到预期的结果呢?

char *p;
strcpy(p, "abc");

其实,这只能说明读者“幸运”。按照前面的分析,未初始化的指针 p 是可能指向任意地址内存的,那么如果它指向的内存允许被上面这两行C语言代码使用,并且这段内存暂时还没有被别的代码使用,程序自然是有可能正常运行的。

总结一下,程序员定义了指针 char *p,也仅仅只是申请了一块用于存放指针 p 自己的内存,如果希望使用 p 管理某段内存,程序员应该显式的申请一块内存,并让 p 指向它。

小明从文件读数据到数组,为什么不正常呢?

相关的C语言代码是下面这样的,小明发现这段代码执行结束后,所有的指针里的内容都是文件最后一行的内容,为什么呢?

    char linebuf[80];
    char *lines[100];
    int i;

    for(i = 0; i < 100; i++) {
        char *p = fgets(linebuf, 80, fp);
        if(p == NULL) break;
        lines[i] = p;
    }


如果读者弄清楚了前面两个问题,再考虑本题就不难了。其实原因很简单,小明只为文件的一行内存申请了内存:linebuf。上面这段C语言代码每次调用 fgets(),上一次读取存放在 linebuf 里的内容都会被覆盖,这种现象一直持续到读到最后一行。

指针数组 lines 的每一个元素指向的内存实际上是同一块内存,都是 linebuf,而最终 linebuf 里的内容最终是文件最后一行的内容,所以 lines 没有将文件所有内容都记录就不足为奇了。

free() 函数怎么知道要释放多少内存?

在C语言程序开发中,malloc() 函数可以申请内存供指针使用。但是小明发现 free() 函数释放 malloc() 返回的指针时,并不需要指定指针指向的内存大小,那么 free() 函数如何确定自己要释放多少内存呢?

其实 malloc/free 库会记录每次 malloc() 申请的内存块,所以调用 free() 函数时,就不必再指定要释放的内存大小了。事实上,大多数 malloc/free 库并不会把“释放(free)”掉的内存返回给操作系统,free(p) 函数仅仅只是告诉操作系统,调用者不打算再占用 p 指向的内存了,因此下次 malloc() 时,可以把 p 指向的内存块分配给其他代码使用。

有时候,读者可能会发现自己的C语言程序明明 free() 了一大块内存,但是通过操作系统资源管理器查看可用内存却并未增加,这就是原因了。

malloc 申请的内存被 free 后,就无法使用了,对不对?

C语言很少限制程序员“自由”,前面一个问题讨论到,很多 malloc/free 库并不会把“释放(free)”掉的内存返回给操作系统,free(p) 仅仅是告诉操作系统,调用者不打算再占用 p 指向的内存了。

但是这段内存仍然还存在于这个世上,通过指针 p,自然也是可以继续访问该内存段的。只不过,这么做是不安全的,因为操作系统随时有可能将这段内存分配给其他代码使用。

虽说很少有C语言程序员会故意使用已经释放的内存内容,但是却很容易“意外的”使用,例如下面这段C语言代码:

struct list *listp, *nextp;
    for(listp = base; listp != NULL; listp = nextp) {
        nextp = listp->next;
        free(listp);
    }


如果不使用临时指针变量 nextp,而是使用更明显的迭代表达式 listp = listp->next,将会发生什么呢?留给读者自己考虑了。

在函数内部使用局部变量接收malloc申请的内存,是否还需要free()呢?

自然是需要的,读者应该明白,C语言中的指针和它指向的内容是两个独立的概念,例如下面这段C语言代码:

void fun()
{
    void *p = malloc(100);
}

虽然局部变量 p 随着函数 fun() 执行结束被释放,但是 p 指向的 100 字节内存却不会被释放,操作系统仍然会认为有人在使用它,不会再将这段内存分配给其他代码使用。如果没有 free(p),这段内存在C语言程序的整个生命周期内,永远都不会再被使用了。

这就是所谓的“内存泄漏”。

如果读者希望申请一段不需要 free() 的内存,可以调用 alloca() 函数。不过,很多有经验的C语言程序员都会规避 alloca() 的使用,因为 alloc() 申请的内存存在于函数栈帧上,嵌入式C语言程序中,函数的栈帧一般都不会太长。

另外,alloca() 函数的实现有些特殊,在一些没有传统栈帧的机器上很难实现。所以 alloca() 函数不是标准的,调用了该函数的C语言程序将丢失可移植性。

小结

虽然C语言的指针可以操作内存,但是这并不意味着指针就是内存。所谓“指针操作内存”,其实只是一种手段,要操作的内存必须已经准备好才行,本节通过几个例子说明了这一点。在文章最后,还讨论了关于动态内存分配可能存在的问题,应该明白,C语言一般不会限制程序员,所以在编写C语言程序时,我们应该清楚每一块内存的使用情况,究竟哪一块已经被释放,哪一块可以安全使用,这些都是需要了然于胸的。

阅读更多:   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