为什么C/C++存在内存问题?

9

我看到很多程序员在编写C/C++代码的时候都会提到与内存相关的问题。我计划学习C/C++编程,我对C/C++有一些初步的了解,但我想看一些简短的示例来了解C/C++为什么会存在内存管理方面的问题。请提供一些示例。


10
因为你需要自己分配和释放内存,所以这样做。 - Mitch Wheat
当你分配内存并释放它时,如何出现问题? - LinuxNewbie
8
@LinuxNewbie的意思是程序员也是人类。 - Luc
10
在使用C和C++编程之间有着很大的区别。尽管它们有语法上的共性和起源(C++来源于C),但是实际上这是两种非常不同的语言。例如,我已经好几年没有在C++中手动删除过内存了,而在C中,你必须一直这样做。 - sbi
1
-1:把C和C++放在同一个问题里是没有意义的。请参考重复问题,C:https://dev59.com/tnVD5IYBdhLWcg3wTJvF C++:https://dev59.com/5HVD5IYBdhLWcg3wI36L - u0b34a0f6ae
显示剩余8条评论
9个回答

17

在C或C++中,有许多方法可以导致内存损坏或泄漏。这些错误有时很难诊断,因为它们通常不容易复现。

例如,未释放您已分配的内存是一个常见错误。例如,以下代码将尝试释放a两次,但失败了,并未释放b

char *a = malloc(128*sizeof(char));
char *b = malloc(128*sizeof(char));
b = a;
free(a);
free(b); // will not free the pointer to the original allocated memory.

下面是一个导致任意内存损坏的缓冲区溢出示例。它是一种缓冲区溢出,因为你不知道 str 的长度。如果它长于 256 字节,那么它将在内存中某个地方写入这些字节,可能会覆盖你的代码,也可能不会。

void somefunc(char *str) {
    char buff[256];
    strcpy(buff, str);
}

我必须克制自己,不去编辑掉这个答案中的C++代码,因为它里面是纯C代码... - Matthieu M.
Strcpy?哎呀。使用std::string,问题解决了。 - Puppy
@fmark:难道不应该是“试图释放两次并失败释放b”吗? - Lazer
@Lazer - 是的!我只是想通过在示例错误中犯一个错误来展示为什么C语言中的内存管理很难。 - fmark
我主张在可能的情况下使用静态内联辅助函数来提高可读性,在这种情况下,它可以修复泄漏问题。作为一个相对新手的C语言开发者,我想象中不必要地覆盖原始b指针的问题是首先重构了一个设计不良的系统所导致的结果。这个问题可以很容易地通过autoregister临时变量来解决,这就是为什么内联函数可以通过参数化来解决这个问题的原因。全局内存需要全局管理。 - AMDG

7
基本上,在这些编程语言中,你需要手动请求每一块不是在编译时已知的局部变量的内存,并且在你不再需要它时手动释放它。有一些库(称为智能指针)可以在一定程度上自动化这个过程,但是它们并不适用于所有情况。此外,通过指针算术方式访问内存的限制是绝对没有的。
手动内存管理可能会导致许多错误:
- 如果你忘记释放某些内存,就会出现内存泄漏。 - 如果你使用比给定指针请求的更多的内存,就会出现缓冲区溢出。 - 如果你释放内存并继续使用“悬空指针”指向它,就会产生未定义行为(通常程序会崩溃)。 - 如果你计算指针算术时出现错误,就会出现崩溃或损坏数据等问题。
而且许多这些问题非常难以诊断和调试。

你能向我展示如何请求一点内存吗?malloc需要整数个字节! - Pete Kirkham
@Pete:嗯,你一次得到8个并不否认我的说法,即你必须手动请求它们,对吧? - Michael Borgwardt
那么我已经访问了每个大陆,因为我已经访问了它们的1/8(以上)? - Pete Kirkham

5
我计划学习C/C++编程。您具体是想学习C语言还是C++语言呢?我不建议同时学习两种语言。从用户的角度来看,C++中的内存管理比C要简单得多,因为大部分都被类封装了起来,例如std :: vector 。从概念上讲,C的内存管理可能更简单一些。基本上只有malloc和free :)

其实我有一个小工具的想法,所以我想用C来实现它(不是C++,因为我认为C++的面向对象概念和设计对我来说有点难)。然后我计划将这个工具转换成C++,这将给我学习C++的机会。我计划(希望)在9~11个月内完成我的工具。 - LinuxNewbie
@Linux:学习C作为迈向C ++的垫脚石 被[Stroustrup](http://www.research.att.com/〜bs/bs_faq.html#prerequisite)建议。 - fredoverflow
1
@LinuxNewbie:C++比C要容易得多,因为那些面向对象的概念将使你免于繁重的工作。 - Puppy

5
我可以诚实地说,在使用C++编程时,我对内存分配没有任何“问题”。我上一次遇到内存泄漏是在10年前,那是由于我的无知和愚蠢。如果你使用RAII、标准库容器和一点点常识编写代码,这个问题真的不存在。

4

在C和C++中,常见的内存管理问题之一与数组缺乏边界检查有关。与Java(例如)不同,C和C++不会检查数组索引是否落在实际数组边界内。因此,很容易意外地覆盖内存。例如(C++):

char *a = new char[10];
a[12] = 'x';

以上代码不会出现编译或运行时错误,但您的代码将覆盖不应该被覆盖的内存。


以上代码不会出现编译或运行时错误。您可以使用更智能的内存例程,在运行时检测缓冲区溢出 ;) - fredoverflow
许多编译器和操作系统在运行时会发出错误,例如Windows上的AV和Visual Studio的CRT中的调试断言。 - Puppy
@FredOverflow @DeadMG:没错,但这两种解决方案都比实际内存被覆盖的时间晚检测到错误。还有更多侵入性的工具(例如valgrind),可以更早地检测到错误。 - Greg Hewgill
但这会导致内存泄漏吗?我认为问题在于您没有调用delete... 最多只会出现“分段错误”问题,而不是内存泄漏问题。 - ShinTakezou
我认为这更多是一个边界检查问题,而不是内存问题。两者都不意味着也不排除另一个。 - Sebastian Mach

3

这个原因通常被认为是C/C++与许多现代语言不同的地方,因为许多现代语言执行内存管理和垃圾回收。在C/C++中,情况并非如此(好或坏)。您需要手动分配和释放内存,如果操作不正确,会导致内存泄漏,在执行内存管理的语言中是不可能出现的。


当程序员分配和释放内存时,为什么需要垃圾回收? - LinuxNewbie
1
@LinuxNewbie:他不会!C++不会自动进行垃圾回收。在这里看看:http://en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29 - nico
2
不一定需要手动分配和释放内存。例如,有一些工具可以减少手动释放内存的需求,比如 boost::shared_ptr<T> - fredoverflow
@FredOverflow:这是手动完成的——只不过手动函数是由其他人编写并由编译器调用的:P - Puppy
@DeadMG:我很高兴Java运行时在Java中手动执行自动free()代码 :P - Sebastian Mach

2
很明显,你需要释放不再使用或者将来不会使用的内存。但是,在程序开始时,你需要确定内存需求是静态的还是在执行时变化的。如果是动态的,你需要为程序工作时所需的内存分配足够的内存,这可能会导致额外的内存消耗。
因此,你需要在不使用内存的情况下释放内存,并在需要时创建内存,就像这样:
struct student
{
 char name[20];
 int roll;
 float marks;
}s[100];

我假设班级里有100名学生。学生人数可能多于100或少于100。如果超过100,则您的程序将丢失信息;如果少于100,则程序将运行但会浪费内存,这可能很大。

因此,我们通常在执行时动态创建记录,就像这样:

struct student *s;

s=(struct student *)malloc(sizeof(struct student));

scanf("%s %d %f",s->name,s->roll,s->marks);

如果不使用,则从内存空间中删除它。

free(s);

这是编程中的良好习惯,如果不从内存中删除,则可能会在某个时候填满内存堆栈并导致系统挂起。


2
当使用new来获取一块内存时,操作系统保留的大小可能比您的请求要大,但永远不会更小。由于这个原因和delete不立即将内存返回给操作系统的事实,当您检查程序正在使用的整个内存时,您可能会认为您的应用程序存在严重的内存泄漏。因此,检查整个程序使用的字节数不能用作检测内存错误的方法。只有当内存管理器指示内存使用量大且持续增长时,您才应该怀疑内存泄漏。

1
到目前为止没有提到的一件事是性能以及为什么你想要手动管理内存。随着程序变得更加复杂(特别是当你使用线程和当内存块的生命周期变得复杂时(即当很难确定何时不需要某个信息时)),准确地管理内存变得很困难,即使使用像valgrind这样强大的现代编程工具也是如此。
那么为什么你想要手动管理内存呢?有几个原因: - 为了理解它是如何工作的 - 要实现垃圾回收/自动内存管理,你需要手动管理内存 - 对于一些低级别的东西,比如内核,你需要手动控制内存的灵活性。 - 最重要的是,如果你正确地进行手动内存管理,你可以获得大量的加速/更低的内存开销(更好的性能)。与垃圾回收相关的一个问题(虽然随着像热点jvm这样更好的垃圾收集器的出现,情况正在改善)是你无法控制内存管理,所以很难处理实时任务(保证某些目标的截止日期,如汽车刹车和起搏器,尝试特殊的实时gc),并且与用户交互的程序可能会卡住一小段时间或者延迟(这对于游戏来说是很糟糕的)。

许多“现代C ++”(据说C ++可以视为多种语言,具体取决于您如何使用它)与使用类的c(或x和y C ++功能)相反,经常使用简单的可选gc /自动内存管理来进行妥协(请注意,可选gc在内存管理方面可能比强制性更差,因为当其强制时,它是一个更简单的系统),以及一些手动内存管理。根据您的做法,使用gc和手动内存管理可能具有一些优点和缺点。可选GC也可用于某些C库,但在C中不太常见。


拥有手动内存工作技能确实有助于您了解GC系统的工作原理...以及何时最好自己完成它。 - Rusty

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接