我一直听说在 C 语言中你必须真正注意如何管理内存。我仍然开始学习 C,但到目前为止,我根本不需要做任何内存管理相关的活动。我总是想象必须释放变量并做各种丑陋的事情。但事实似乎并非如此。

有人可以向我展示(带有代码示例)一个何时必须进行“内存管理”的示例吗?

有帮助吗?

解决方案

有两个地方可以将变量放入内存中。当您创建这样的变量时:

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 gig的内存(可能更多,取决于你的内存管理器),如果你没有崩溃你的进程可能运行得很慢。

要修复,您需要在完成使用后使用malloc()获取的所有内容调用free():

s = strdup("hello");
free(s);  /* now not leaking memory! */
s = strdup("world");
...

希望这个例子有所帮助!

你必须做“内存管理”。当你想在堆而不是堆栈上使用内存时。如果您不知道在运行时创建数组有多大,那么您必须使用堆。例如,您可能希望将某些内容存储在字符串中,但在程序运行之前不知道其内容的大小。在这种情况下,你会写这样的东西:

 char *string = malloc(stringlength); // stringlength is the number of bytes to allocate

 // Do something with the string...

 free(string); // Free the allocated memory

我认为最简洁的方式来回答这个问题,以考虑指针在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";

你将分配“abcde”你已经分配给你的程序的5个字节,并且尾随的空字符将放在这个结尾 - 一部分内存尚未分配给你使用并且可以是免费的,但同样可能是另一个应用程序使用 - 这是内存管理的关键部分,错误将导致不可预测的(有时是不可重复的)后果。

要记住的是总是将指针初始化为NULL,因为未初始化的指针可能包含伪随机有效内存地址,这会使指针错误无声地继续。通过强制使用NULL初始化指针,您可以始终捕获是否使用此指针而不初始化它。原因是操作系统“线”是指操作系统。虚拟地址0x00000000到一般保护异常以捕获空指针的使用。

当你需要定义一个庞大的数组时,你可能想要使用动态内存分配,比如int [10000]。你不能把它放在堆栈中,因为那样,嗯......你会得到堆栈溢出。

另一个很好的例子是数据结构的实现,比如链表或二叉树。我没有要粘贴的示例代码,但您可以轻松地进行谷歌搜索。

(我写这篇文章是因为我觉得到目前为止的答案还不太切中要害。)

值得一提的内存管理的原因是当您遇到需要创建复杂结构的问题/解决方案时。(如果您一次在堆栈上分配了很多空间,那么您的程序就会崩溃,这就是一个错误。)通常,您需要学习的第一个数据结构是某种 列表. 。这是我脑海中的一个链接:

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;
}

当然,您还需要一些其他功能,但基本上,这就是您需要内存管理的目的。我应该指出,“手动”内存管理可以使用许多技巧,例如,

  • 利用这一事实 分配内存 保证(根据语言标准)返回一个能被 4 整除的指针,
  • 为自己的某些邪恶目的分配额外的空间,
  • 创造 内存池..

获得一个好的调试器... 祝你好运!

@ Euro Micelli

要添加的一个消极因素是,当函数返回时,指向堆栈的指针不再有效,因此您无法从函数返回指向堆栈变量的指针。这是一个常见错误,也是您无法使用堆栈变量的主要原因。如果你的函数需要返回一个指针,那么你必须使用malloc并处理内存管理。

  

@ Ted Percival
  ...你不需要转换malloc()的返回值。

当然,你是对的。我相信这一直是真的,虽然我没有 K&amp; R 来检查。

我不喜欢C中的很多隐式转换,所以我倾向于使用强制转换来制作“魔术”。更明显。有时它有助于提高可读性,有时却没有,有时它会导致编译器捕获到无声错误。不过,我对这方面的看法不是那么强烈。

  

如果您的编译器理解C ++风格的注释,则特别有可能。

是的......你在那里抓住了我。我花了很多时间在C ++而不是C.感谢你注意到。

在C中,您实际上有两种不同的选择。一,您可以让系统为您管理内存。或者,你可以自己做。一般来说,你会希望尽可能长时间地坚持前者。但是,C中的自动管理内存非常有限,在许多情况下您需要手动管理内存,例如:

一个。您希望变量比函数更长,并且您不希望拥有全局变量。例如:

struct pair{
   int val;
   struct pair *next;
}

struct pair* new_pair(int val){
   struct pair* np = malloc(sizeof(struct pair));
   np->val = val;
   np->next = NULL;
   return np;
}

湾你想拥有动态分配的内存。最常见的例子是没有固定长度的数组:

int *my_special_array;
my_special_array = malloc(sizeof(int) * number_of_element);
for(i=0; i

c. You want to do something REALLY dirty. For example, I would want a struct to represent many kind of data and I don't like union (union looks soooo messy):

struct data{ int data_type; long data_in_mem; }; struct animal{/*something*/}; struct person{/*some other thing*/}; struct animal* read_animal(); struct person* read_person(); /*In main*/ struct data sample; sampe.data_type = input_type; switch(input_type){ case DATA_PERSON: sample.data_in_mem = read_person(); break; case DATA_ANIMAL: sample.data_in_mem = read_animal(); default: printf("Oh hoh! I warn you, that again and I will seg fault your OS"); }

看,长值足以容纳任何东西。只要记得释放它,否则你会后悔的。这是我最喜欢在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对象用于多个函数中,因此我动态分配了内存:SomeCther对象是在创建MyClass时创建的,在对象的生命周期内多次使用,然后在释放MyClass后释放。

显然,如果这是真正的代码,那么就没有理由(除了可能的堆栈内存消耗)以这种方式创建myObject,但是当你有很多对象时,这种类型的对象创建/破坏会变得很有用精确控制它们何时被创建和销毁(例如,你的应用程序在其整个生命周期中不会吸收1GB的RAM),并且在Windowed环境中,这几乎是强制性的,因为你创建的对象(按钮) ,比如说),需要在任何特定函数(甚至是类)范围之外存在。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top