我一直听说,在C语言中,您必须真正观察如何管理内存。而且我仍然开始学习C,但是到目前为止,我根本不需要做任何内存管理相关的活动。.我一直想像必须释放变量并做各种丑陋的事情。但这似乎并非如此。
有人可以通过代码示例向我展示您何时需要执行“内存管理”的示例?
Answers:
可以在两个地方将变量放入内存。创建这样的变量时:
int a;
char c;
char d[16];
变量在“ 堆栈 ” 中创建。当堆栈变量超出范围时(即,代码不再到达它们时),堆栈变量将自动释放。您可能会听到它们被称为“自动”变量的信息,但是这已经过时了。
许多初学者的示例将仅使用堆栈变量。
堆栈很好,因为它是自动的,但它也有两个缺点:(1)编译器需要事先知道变量的大小,并且(b)堆栈空间有限。例如:在Windows中,在Microsoft链接器的默认设置下,堆栈设置为1 MB,并非所有变量都可用。
如果在编译时不知道数组有多大,或者如果需要大型数组或结构,则需要“计划B”。
计划B称为“ 堆 ”。通常,您可以创建与操作系统允许的变量一样大的变量,但是您必须自己进行操作。较早的帖子向您显示了一种实现方法,尽管还有其他方法:
int size;
// ...
// Set size to some value, based on information available at run-time. Then:
// ...
char *p = (char *)malloc(size);
(请注意,堆中的变量不是直接操作,而是通过指针操作)
一旦创建了堆变量,问题就在于编译器无法告知您何时使用完它,因此您将失去自动释放的能力。这就是您所指的“手动释放”的地方。您的代码现在负责确定何时不再需要该变量,然后释放它,以便将内存用于其他目的。对于上述情况,使用:
free(p);
使第二种选择成为“令人讨厌的业务”的原因在于,何时不再需要该变量并不总是很容易知道。忘记在不需要变量时释放它会导致程序消耗更多的内存。这种情况称为“泄漏”。在程序结束并且操作系统恢复其所有资源之前,“泄漏的”内存无法用于任何用途。如果在实际使用堆变量之前不小心释放了堆变量,那么甚至可能会出现更棘手的问题。
在C和C ++中,您有责任清理如上所示的堆变量。但是,有些语言和环境(例如Java和.NET语言,如C#)使用不同的方法,其中堆会自行清理。第二种方法称为“垃圾收集”,对开发人员来说要容易得多,但是您要付出开销和性能上的损失。这是一个平衡。
(我已经掩盖了许多细节,以给出一个更简单但希望更平均的答案)
这是一个例子。假设您有一个strdup()函数来复制字符串:
char *strdup(char *src)
{
char * dest;
dest = malloc(strlen(src) + 1);
if (dest == NULL)
abort();
strcpy(dest, src);
return dest;
}
您这样称呼它:
main()
{
char *s;
s = strdup("hello");
printf("%s\n", s);
s = strdup("world");
printf("%s\n", s);
}
您可以看到该程序正常运行,但是您已经通过malloc分配了内存,而没有释放它。当您第二次调用strdup时,您已经失去了指向第一个内存块的指针。
对于如此少量的内存,这没什么大不了的,但请考虑以下情况:
for (i = 0; i < 1000000000; ++i) /* billion times */
s = strdup("hello world"); /* 11 bytes */
现在,您已经用完了11 GB的内存(可能更多,具体取决于您的内存管理器),并且如果您没有崩溃,则进程可能运行缓慢。
要解决此问题,在使用完malloc()之后,您需要为它调用的所有函数调用free():
s = strdup("hello");
free(s); /* now not leaking memory! */
s = strdup("world");
...
希望这个例子对您有所帮助!
我认为,最简单的回答问题的方式就是考虑指针在C中的作用。指针是一种轻量级但功能强大的机制,它以极大的能力为自己射击,为您提供了极大的自由。
在C语言中,确保您的指针指向您拥有的内存的责任仅属于您自己。除非您放弃了指针,否则这就需要一种有组织且有纪律的方法,这使得编写有效的C语言变得困难。
迄今为止发布的答案集中在自动(堆栈)和堆变量分配上。使用堆栈分配确实可以实现自动管理和方便的内存,但是在某些情况下(大缓冲区,递归算法),这可能会导致可怕的堆栈溢出问题。确切知道可以在堆栈上分配多少内存在很大程度上取决于系统。在某些嵌入式方案中,几十个字节可能是您的限制,在某些台式机方案中,您可以安全地使用兆字节。
堆分配不是该语言固有的。基本上,它是一组库调用,可以授予您给定大小的内存块的所有权,直到您准备好返回(“释放”)它为止。听起来很简单,但是却伴随着无数程序员的悲伤。问题很简单(两次释放相同的内存,或者根本不释放[内存泄漏],没有分配足够的内存[缓冲区溢出],等等),但是很难避免和调试。严格遵守纪律的方法绝对是必不可少的,但当然,语言实际上并没有强制性。
我想提及另一种类型的内存分配,该类型已被其他帖子忽略。通过在任何函数外部声明变量,可以静态分配变量。我认为一般来说,这种分配方式很糟糕,因为它被全局变量使用。但是,没有什么可以说使用这种方式分配的内存的唯一方法是在混乱的意大利面条代码中将其作为不规则的全局变量。静态分配方法可以简单地用于避免堆和自动分配方法的某些陷阱。一些C程序员惊讶地发现,大型且复杂的C嵌入式和游戏程序是在完全不使用堆分配的情况下构建的。
关于如何分配和释放内存,这里有一些很好的答案,在我看来,使用C的更具挑战性的一面是确保您使用的唯一内存是您分配的内存-如果这样做不正确,您的最终结果将是什么?这个站点的堂兄(缓冲区溢出),您可能正在覆盖另一个应用程序正在使用的内存,结果非常不可预测。
一个例子:
int main() {
char* myString = (char*)malloc(5*sizeof(char));
myString = "abcd";
}
此时,您已经为myString分配了5个字节,并用“ abcd \ 0”填充(字符串以null结尾-\ 0)。如果您的字符串分配是
myString = "abcde";
您将在分配给程序的5个字节中分配“ abcde”,结尾的空字符将放在此末尾-尚未分配给您使用的一部分内存,可以免费的,但同样可以被其他应用程序使用-这是内存管理的关键部分,其中的错误将带来不可预测的(有时是不可重复的)后果。
strcpy()
代替=
;我认为这是克里斯·BC的意图。
(我写这封信是因为我觉得到目前为止答案还不十分清楚。)
内存管理值得一提的原因是当您遇到需要创建复杂结构的问题/解决方案时。(如果您一次在堆栈上分配大量空间而导致程序崩溃,那是一个错误。)通常,您需要学习的第一个数据结构是某种list。这是一个链接的链接,位于我的头顶上:
typedef struct listelem { struct listelem *next; void *data;} listelem;
listelem * create(void * data)
{
listelem *p = calloc(1, sizeof(listelem));
if(p) p->data = data;
return p;
}
listelem * delete(listelem * p)
{
listelem next = p->next;
free(p);
return next;
}
void deleteall(listelem * p)
{
while(p) p = delete(p);
}
void foreach(listelem * p, void (*fun)(void *data) )
{
for( ; p != NULL; p = p->next) fun(p->data);
}
listelem * merge(listelem *p, listelem *q)
{
while(p != NULL && p->next != NULL) p = p->next;
if(p) {
p->next = q;
return p;
} else
return q;
}
当然,您还需要其他一些功能,但是基本上,这是您需要内存管理的功能。我应该指出,“手动”内存管理有许多技巧,例如,
获得一个好的调试器...祝您好运!
@ 欧元米切利
要补充的一个缺点是,当函数返回时,指向堆栈的指针不再有效,因此您不能从函数返回指向堆栈变量的指针。这是一个常见错误,也是仅使用堆栈变量就无法实现的主要原因。如果您的函数需要返回指针,则必须进行malloc并处理内存管理。
@ Ted Percival:
...您不需要强制转换malloc()的返回值。
你是正确的,当然。尽管我没有要检查的K&R副本,但我相信这始终是正确的。
我不喜欢C中的许多隐式转换,因此我倾向于使用强制类型转换使“魔术”更加可见。有时它有助于提高可读性,有时却不能提高可读性,有时它会导致编译器捕获无提示错误。尽管如此,我对此并没有强烈的看法。
如果您的编译器理解C ++样式的注释,则这尤其可能。
是的...你抓到我了。我在C ++上花费的时间比C多得多。感谢您注意到这一点。
在C语言中,您实际上有两个不同的选择。一,您可以让系统为您管理内存。或者,您可以自己执行此操作。通常,您希望尽可能长地坚持前者。但是,C中的自动管理内存非常有限,在许多情况下,您将需要手动管理内存,例如:
一个。您希望变量的寿命超过函数,并且您不希望拥有全局变量。例如:
结构对{ int val; 结构对* next; } 结构对* new_pair(int val){ 结构对* np = malloc(sizeof(结构对)); np-> val = val; np-> next = NULL; 返回np }
b。您想要动态分配内存。最常见的示例是没有固定长度的数组:
诠释* my_special_array; my_special_array = malloc(sizeof(int)* number_of_element); for(i = 0;我C。你想做些很脏的事。例如,我想要一个结构体来表示许多类型的数据,而我不喜欢联合(工会看起来太乱了):
结构数据{ int data_type; long data_in_mem; }; 结构动物{/ * something * /}; 构造人{/ *其他东西* /}; struct animal * read_animal(); struct person * read_person(); / *在主* / 结构数据样本; sampe.data_type = input_type; 开关(input_type){ 情况DATA_PERSON: sample.data_in_mem = read_person(); 打破; 情况DATA_ANIMAL: sample.data_in_mem = read_animal(); 默认: printf(“哦,哦!我警告你,那我会再次对你的操作系统造成错误”); }
瞧,一个长值足以容纳任何东西。请记住要释放它,否则您将后悔。这是我最喜欢的C:D技巧。
但是,通常,您可能希望远离自己喜欢的技巧(T___T)。如果使用频率过高,您迟早会破坏操作系统。只要您不使用* alloc和free,就可以肯定地说您仍然是处女,并且代码看起来仍然不错。
当然。如果创建的对象不在范围内,则可以在其中使用它。这是一个人为的示例(请记住,我的语法将关闭; C处于生锈状态,但此示例仍将说明该概念):
class MyClass
{
SomeOtherClass *myObject;
public MyClass()
{
//The object is created when the class is constructed
myObject = (SomeOtherClass*)malloc(sizeof(myObject));
}
public ~MyClass()
{
//The class is destructed
//If you don't free the object here, you leak memory
free(myObject);
}
public void SomeMemberFunction()
{
//Some use of the object
myObject->SomeOperation();
}
};
在此示例中,我在MyClass的生命周期内使用SomeOtherClass类型的对象。SomeOtherClass对象在多个函数中使用,因此我已经动态分配了内存:SomeOtherClass对象是在创建MyClass时创建的,在对象的生命周期中使用了几次,然后在MyClass释放后释放。
显然,如果这是真实的代码,则没有理由(除了可能消耗堆栈内存)以这种方式创建myObject,但是当您有很多对象并且想要精细地控制时,这种类型的对象创建/销毁就非常有用。在创建和销毁它们时(例如,使您的应用程序在整个生命周期内都不会占用1GB的RAM),并且在Windowed环境中,这对于创建的对象(例如按钮)来说几乎是强制性的,必须存在于任何特定函数(甚至类)的范围之外。