本文将带您了解一些良好的和内存相关的编码实践,以将内存错误保持在控制范围内。内存错误是 C 和 C++ 编程的祸根:它们很普遍,认识其严重性已有二十多年,但始终没有彻底解决,它们可能严重影响应用程序,并且很少有开发团队对其制定明确的管理计划。但好消息是,它们并不怎么神秘。
void f1(char *explanation)
{
char p1;
p1 = malloc(100);
(void) sprintf(p1,
"The f1 error occurred because of '%s'.",
explanation);
local_log(p1);
}
您看到问题了吗?除非 local_log()对 free()释放的内存具有不寻常的响应能力,否则每次对 f1的调用都会泄漏 100 字节。在记忆棒增量分发数兆字节内存时,一次泄漏是微不足道的,但是连续操作数小时后,即使如此小的泄漏也会削弱应用程序。
int getkey(char *filename)
{
FILE *fp;
int key;
fp = fopen(filename, "r");
fscanf(fp, "%d", &key);
return key;
}
fopen的语义需要补充性的 fclose。在没有 fclose()的情况下,C 标准不能指定发生的情况时,很可能是内存泄漏。其他资源(如信号量、网络句柄、数据库连接等)同样值得考虑。
内存错误分配
void f2(int datum)
{
int *p2;
/* Uh-oh! No one has initialized p2. */
*p2 = datum;
...
}
关于此类错误的好消息是,它们一般具有显著结果。在 AIX 下,对未初始化指针的分配通常会立即导致 segmentation fault错误。它的好处是任何此类错误都会被快速地检测到;与花费数月时间才能确定且难以再现的错误相比,检测此类错误的代价要小得多。
/* Allocate once, free twice. */
void f3()
{
char *p;
p = malloc(10);
...
free(p);
...
free(p);
}
/* Allocate zero times, free once. */
void f4()
{
char *p;
/* Note that p remains uninitialized here. */
free(p);
}
这些错误通常也不太严重。尽管 C 标准在这些情形中没有定义具体行为,但典型的实现将忽略错误,或者快速而明确地对它们进行标记;总之,这些都是安全情形。
悬空指针
悬空指针比较棘手。当程序员在内存资源释放后使用资源时会发生悬空指针(请参见清单 5):
清单 5. 悬空指针
void f8()
{
struct x *xp;
xp = (struct x *) malloc(sizeof (struct x));
xp.q = 13;
...
free(xp);
...
/* Problem! There's no guarantee that
the memory block to which xp points
hasn't been overwritten. */
return xp.q;
}
传统的“调试”难以隔离悬空指针。由于下面两个明显原因,它们很难再现:
/********
* ...
*
* Note that any function invoking protected_file_read()
* assumes responsibility eventually to fclose() its
* return value, UNLESS that value is NULL.
*
********/
FILE *protected_file_read(char *filename)
{
FILE *fp;
fp = fopen(filename, "r");
if (fp) {
...
} else {
...
}
return fp;
}
/*******
* ...
*
* Note that the return value of get_message points to a
* fixed memory location. Do NOT free() it; remember to
* make a copy if it must be retained ...
*
********/
char *get_message()
{
static char this_buffer[400];
...
(void) sprintf(this_buffer, ...);
return this_buffer;
}
/********
* ...
* While this function uses heap memory, and so
* temporarily might expand the over-all memory
* footprint, it properly cleans up after itself.
*
********/
int f6(char *item1)
{
my_class c1;
int result;
...
c1 = new my_class(item1);
...
result = c1.x;
delete c1;
return result;
}
/********
* ...
* Note that f8() is documented to return a value
* which needs to be returned to heap; as f7 thinly
* wraps f8, any code which invokes f7() must be
* careful to free() the return value.
*
********/
int *f7()
{
int *p;
p = f8(...);
...
return p;
}
使这些格式元素成为您日常工作的一部分。可以使用各种方法解决内存问题:专用库语言软件工具
static char *important_pointer = NULL;
void f9()
{
if (!important_pointer)
important_pointer = malloc(IMPORTANT_SIZE);
...
if (condition)
/* Ooops! We just lost the reference
important_pointer already held. */
important_pointer = malloc(DIFFERENT_SIZE);
...
}
如果 condition为真,简单使用自动运行时工具不能检测发生的内存泄漏。仔细进行源分析可以从此类条件推理出证实正确的结论。我重复一下我写的关于风格的内容:尽管大量发布的内存问题描述都强调工具和语言,对于我来说,最大的收获来自“软的”以开发人员为中心的流程变更。您在风格和检测上所做的任何改进都可以帮助您理解由自动化工具产生的诊断。
静态的自动语法分析
当然,并不是只有人类才能读取源代码。您还应使静态语法分析成为开发流程的一部分。静态语法分析是 lint、严格编译和几种商业产品执行的内容:扫描编译器接受的源文本和目标项,但这可能是错误的症状。
int main()
{
char p[5];
strcpy(p, "Hello, world.");
puts(p);
}
此程序可以在许多环境中“运行”,它编译、执行并将“Hello, world.n”打印到屏幕。使用内存工具运行相同应用程序会在第四行产生一个数组边界违规的报告。在了解软件错误(将十四个字符复制到了只能容纳五个字符的空间中)方面,这种方法比在客户处查找错误症状的花费小得多。这是内存工具的功劳。
全部0条评论
快来发表一下你的评论吧 !