文章目录
概要
内存管理中的三大常见问题:内存越界、内存溢出和内存泄露,它们各自具有独特的特征和解决方案。
a. 内存越界:
内存越界指的是程序在访问已分配内存时,超出了其合法边界范围。
典型场景如:声明了一个长度为10的数组,却试图访问第11个元素。这种错误往往在编译阶段难以被检测到,而是在程序运行时引发不可预知的行为,严重时可能导致系统崩溃。
b. 内存溢出:
内存溢出(Out of Memory,简称OOM)是指程序在运行过程中尝试分配内存时,由于系统可用内存不足而无法满足其需求的现象。
以Java为例,
- 堆内存溢出通常由创建过多对象导致堆内存耗尽引发;
- 栈内存溢出则多因递归调用过深致使栈内存耗尽。内存溢出会直接导致程序运行异常,通常需要通过增加系统内存或优化代码来降低内存消耗。
c. 内存泄露:
内存泄露是指程序在申请内存后未能正确释放已占用的内存空间,导致系统可用内存逐渐减少。
例如,在C或C++中,使用malloc分配内存后未执行对应的free操作,或使用new创建对象后未进行delete操作,都会引发内存泄露。
内存泄露会持续消耗系统内存,最终可能导致内存溢出。
内存越界(Memory out-of-bounds analysis)
提示:内存越界,一般是会影响到隔壁的变量(如:data/bss静态变量,以及高、中地址栈局部变量和堆动态内存分配变量),那么我们可以直接分析.map文件和定位代码逻辑等。
借用一遍写的很好的文章,大家可以直接了解一下:
内存溢出(Memory Overflow Analysis)
内存溢出是指程序在运行时尝试分配内存,但由于没有足够的内存可用, 比如:Java虚拟机 (JVM)抛出 OutOfMemoryError 错误。
a. 递归调用导致栈溢出:
public class StackOverflowExample {
public static void main(String[] args) {
testStackOverflow();
}
public static void testStackOverflow() {
testStackOverflow();
}
}
这段代码会导致递归调用无限进行,最终耗尽虚拟机栈,导致栈溢出。
b. 无限创建对象导致堆溢出:
public class HeapOverflowExample {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
while (true) {
list.add(1);
}
}
}
这段代码会无限创建 Integer对象 ,最终耗尽堆内存,导致堆溢出。
内存泄露(Memory Leakage Analysis)
提示: 在C或C++中,使用malloc分配内存后未执行对应的free操作,或使用new创建对象后未进行delete操作,都会引发内存泄露。
int main(void)
{
int *p_mem1 = (int*) malloc( sizeof(int) ); //内存块1
int *p_mem2 = (int*) malloc( sizeof(int) ); //内存块2
p_mem2 = p_mem1;
//free(p_mem1)和free(p_mem2)只能调用一个
free(p_mem1);
//free(p_mem2);
return 0;
}
//或者:
int main(void)
{
int *p_mem1 = (int*) malloc( sizeof(int) ); //内存块1
int *p_mem2 = (int*) malloc( sizeof(int) ); //内存块2
//free(p_mem1)和free(p_mem2) 仅仅调用了其中一个
free(p_mem1);
//free(p_mem2);
return 0;
}
大家也可以参考一遍文章:
一文彻底搞清楚内存泄漏(新手必看)
内存重叠(Memory Overlap Analysis)
内存重叠是指不同指针访问的内存区域存在交叉部分,在进行数据复制或移动时可能导致数据覆盖或错误。
代码示例:
char str[] = "Hello World";
char *p = str + 1;
memcpy(p, str, 11);
这段代码定义了一个字符数组 str,并用指针 p 指向 str 的第二个字符。
随后调用 memcpy 函数将 str 中的 11 个字符复制到 p 指向的内存区域。由于 p 指向的地址与 str 存在交叠,导致了内存重叠问题。
具体表现为:当 memcpy 复制到 str:ml-citation{ref=“1” data=“citationList”} 时,会覆盖该位置的原始内容,进而影响后续的复制操作。
- 为预防内存重叠问题,建议采取以下措施:
a. 减少指针使用,特别是避免指针运算和类型转换
确保指针所指向的内存区域独立不重叠
b. 采用安全的内存操作函数,如具有重叠处理能力的 memcpy_s 和 memmove。
c. 关于 memmove 的内存重叠处理机制:
memmove 函数通过特殊设计可安全处理源地址与目标地址间的内存重叠情况。
其实现方式如下:
void* my_memmove(void* dest, const void* src, size_t n)
{
uint8_t* d = (uint8_t*)dest;
const uint8_t* s = (const uint8_t*)src;
if (d == s) {
return dest;
}
if (d < s) {
for (size_t i = 0; i < n; i++) {
d[i] = s[i];
}
} else {
for (size_t i = n; i != 0; i--) {
d[i-1] = s[i-1];
}
}
return dest;
}
本实现采用智能复制策略:当目标地址位于源地址前方时执行正向复制;若目标地址在源地址后方则采用逆向复制,有效防止重叠区域数据被覆盖。
解决方案
- 代码优化:通过检查代码中的大对象创建、递归调用等操作,有效减少不必要的内存消耗。
- 工具辅助:借助专业的内存分析工具,及时检测并修复潜在的内存泄露问题。
- 硬件升级(类似针对电脑设备等):对于频繁出现内存溢出的情况,可考虑提升系统的物理内存容量。
- 内存管理:在开发过程中,注重内存的合理申请与释放,防止内存泄露和越界等问题的发生。
成功之路从来不会一帆风顺,质疑与挑战如影随形。唯有坚定信念、勇往直前,才是我们唯一的选择。
O(∩_∩)O哈哈~