65.9K
CodeProject 正在变化。 阅读更多。
Home

C 中的分配内存管理

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.31/5 (5投票s)

2016年4月14日

CPOL

5分钟阅读

viewsIcon

13501

处理动态分配的

引言

在 C 语言编程中,动态分配内存的处理是一大痛点。您必须为每次分配的内存调用 malloc()。但更重要的是,当不再需要该变量时,您必须 free() 它。许多 bug 都源于对这些指令的不当处理。最理想的情况是您只是忘记了 free()。但有时,bug 会隐藏起来。例如,您覆盖了一个包含指针的变量,导致指针丢失。在 C 语言中,这还会影响程序的架构。例如,在我上一篇文章 https://codeproject.org.cn/Tips/1091150/Fast-Graph-Traversal 中,我假设存在一个包含所有节点和一个包含所有边的数组。这种在其他语言中可能无用的架构解决方案在 C 语言中是强制性的。没有办法 free() 一个图,因为根据定义,图没有根。通过我的算法,也许您可以将其分解为一棵树,但同时,您需要编写另一个程序来 free() 这个图。

预处理器

C 语言提供了一个有用的工具,称为预处理器指令。如果您想知道预处理器是做什么的,我猜您是初学者。所以我不打算给出学术定义,而是给出一个易于理解的定义。

预处理器指令是帮助程序员编写代码的工具,其附带效应是提高程序的性能。因此,如果它有助于编写代码,意味着它作用于文本形式的人类可读代码。它正是这样做的,它用另一个文本替换某个文本。它只是愚蠢地比较 string 并替换它们。为什么会提高性能?因为您也可以声明一些“函数”,这些函数称为宏,而不是使用效率较低的函数调用,它只是用您的指令替换宏。传统上,getc()putc() 函数是宏,因为它们不是在处理文本中的每个字符时调用函数,而是在本地进行评估。

所以我们说这些是编写代码的工具,是为了帮助程序员,而本文的重点正是如此。帮助程序员处理变量,并在发布前检测一些错误。最好在发布时,移除所有这些指令。

我们将使用哪些指令?

#define abs(foo) (foo)>=0?(foo):(-foo) 这只是一个例子。这个宏做什么?它只是将 abs() 的每个出现都替换为相应的指令 (foo)>=0?(foo):(-foo),请注意没有分号。因为它只是替换文本,如果您加上分号,它也会在您的代码中添加分号,但这可能是不需要的。

__LINE__ 这是一个标准的 ANSI 宏,它将单词 __LINE__ 替换为相应的行号。

__FILE__ 也同样是标准的,它将单词 __FILE__ 替换为相应的文件名(例如:FOO.hmain.h)。

#undef abs(foo) 停止对已定义的宏的定义。因此,从这一点开始,您的宏将不再被替换。

有时,我总觉得有一种反对预处理器指令的战争。C++ 的纯粹主义者自豪地说,这些指令将变得多余。每次听到这样的评论,我都会问自己:“你们怎么了?” 为什么要反对一个工具?为什么要反对一个旨在帮助您的工具?因为这就是宏的范围:帮助程序员。C++ 可能想要的是提供一种替代宏副作用的方法:提高性能。我们将要做的,无法通过内联函数或常量来实现。我们需要一种愚蠢地替换文本的工具。但 C++ 的内联函数并非多余。如果我有一个大函数,我不太喜欢用宏来写。例如:fast graph traversal 文章中的 next_row() 函数,我会将其声明为内联。它会被重复n 次,但为了提高可读性,我选择将其写在主函数之外,并且它太大了,无法用宏实现。

所以每个工具都有其工作范围。我真正不理解的是 const - 这完全不必要。

Using the Code

我们假设您已经声明了哈希表例程。如果您没有……那就不太好了。您必须搜索代码并编写一个。

那么范围是什么:我们将所有 malloc 调用重定向到我们的自定义函数,在该函数中我们实际分配了内存,但我们也添加了新分配的指针的地址名称,以及相应的文件和行标签。这样,我们就可以随时知道哪些变量已被分配。并且如果在每次调用 free() 时,我们 pop() 相同的变量,当调用退出例程时,应该没有人被分配。但如果有人在那里,我们就知道是谁在哪里分配的。并将您的调试简化为局部研究。

首先,重定向 malloc() (同样,您必须对处理内存的每个函数都这样做:strdup()calloc()realloc(),在后一种情况下,您必须弹出当前指针并添加新的)。

#define malloc(a) smalloc((a),__FILE__,__LINE__)

这会将您所有对 malloc 的调用替换为我们的自定义函数 smalloc(),它接受三个参数,而无需修改您的任何代码行。当您在发布前移除此宏时,一切将恢复正常。

我们也对 free() 做了同样的处理。

#define free(a) sfree((a),__FILE__,__LINE__)

好的,现在我们必须编写我们的函数 smalloc()sfree(),但在这样做之前,我们必须将指针的地址转换为人类可读的 string。否则,就无法计算地址的哈希值。为此,我们使用了 union 的属性。它们在相同的内存“槽”中分配两个或多个变量。如果您放入一个变量,并用另一个变量访问它,您就知道当它具有另一个 datatype 时它会如何显示……(定义非常接近)。

union pointer_translator
{
	void * pointer;
	unsigned value; //if int or long depends from the architecture.
}

我们构建我们的 struct 来推入 hashtable:a

struct file_line 
{
 	char * file;
	unsigned line;
 } 

瞧!我们写了函数。

//first we undefine the macros locally, or we are going to have a infinite loop:
#undef malloc(a)
#undef free(a)
void *smalloc(size_t sz, char *fl, unsigned l)
{
	char st[10];
	void *res=malloc(sz);
	struct pointer_translator pt;
	pt.pointer=res;
	atoi(st,pt.value,10);
	struct file_line* f_l=(struct file_line*)malloc(sizeof(struct file_line));	
	f_l->file=strdup(fl);
	f_l->line=l;	
	hadd(hshtbl, st,f_l);
	return res; 
}
//similarly for free()
void sfree(void * p, char *fl, unsigned l)
{
	char st[10];
	struct file_line *fl;
	struct pointer_translator pt;
	pt.pointer=p;
	atoi(st,pt.value,10);
	fl=(struct file_line *)hpop(hshtbl,st);
	if (!fl)	
		printf("%s %d\n",fl,l);
	destroy_fl(fl);	
	free(p);

}
//now we redifine the macros
#define malloc(a) smalloc((a),__FILE__,__LINE__) 
#define free(a) sfree((a),__FILE__,__LINE__) 

请记住,也要在哈希表代码内部取消宏的定义。

这就是全部了……祝大家 C 语言编程愉快。

© . All rights reserved.