内存管理

comeweijjd枫长LH...大约 16 分钟

1. 大端序与小端序?

大端序(Big-endian)和小端序(Little-endian)是计算机存储多字节数据类型(如整数、浮点数等)时使用的两种字节序。它们描述了多字节数据在内存中的存储顺序。不同的硬件架构和操作系统可能采用不同的字节序,这在跨平台通信和数据交换时可能导致问题。

  • 大端序(Big-endian): 在大端序中,数据的高位字节(最重要的字节)存储在较低的内存地址中,而数据的低位字节(最不重要的字节)存储在较高的内存地址中。换句话说,字节序是从高位到低位。例如,32位整数 0x12345678 在大端序中的存储顺序是:
内存地址: 0x01 0x02 0x03 0x04
存储值  : 0x12 0x34 0x56 0x78
  • 小端序(Little-endian): 在小端序中,数据的低位字节(最不重要的字节)存储在较低的内存地址中,而数据的高位字节(最重要的字节)存储在较高的内存地址中。换句话说,字节序是从低位到高位。例如,32位整数 0x12345678 在小端序中的存储顺序是:
内存地址: 0x01 0x02 0x03 0x04
存储值  : 0x78 0x56 0x34 0x12

在处理跨平台数据交换时,了解大端序和小端序是很重要的。为了避免字节序问题,可以使用一种通用的字节序,如网络字节序(大端序),在发送和接收数据时进行字节序转换。

2. 内存的分配方式有几种?

静态内存分配(Static Memory Allocation): 静态内存分配是在编译时确定内存大小和位置的分配方式。全局变量、静态变量和常量都使用静态内存分配。这些变量在程序的整个生命周期中都存在,直到程序结束。静态内存分配的优点是速度快,开销小;缺点是分配的内存大小在编译时就确定,不能在运行时改变。

栈内存分配(Stack Memory Allocation): 栈内存分配是在函数调用期间为局部变量和函数参数分配内存的方式。栈内存分配由编译器自动处理,无需程序员手动管理。栈内存分配速度快,但分配的内存大小受到栈大小的限制。当函数调用结束时,分配的内存会自动释放。

堆内存分配(Heap Memory Allocation): 堆内存分配是在程序运行期间动态分配和释放内存的方式。C++中,可以使用newdelete操作符(或new[]delete[]操作符)在堆上分配和释放内存。堆内存分配允许在运行时分配可变大小的内存,但分配和释放的速度相对较慢,且需要程序员手动管理内存的生命周期。不正确地管理堆内存可能导致内存泄漏或悬挂指针等问题。

总结:在C++中,有三种常见的内存分配方式:静态内存分配、栈内存分配和堆内存分配。静态内存分配和栈内存分配速度快,但内存大小和生命周期在编译时或函数调用期间确定;堆内存分配允许在运行时分配可变大小的内存,但速度较慢,且需要手动管理内存。

3. 局部变量在内存中如何被分配?是否产生符号?

局部变量在内存中是通过栈(Stack)进行分配的。当一个函数被调用时,会为该函数创建一个栈帧(Stack Frame)。栈帧中包含了该函数的局部变量、函数参数以及其他与函数调用相关的信息。局部变量在栈上分配内存是由编译器自动处理的,函数调用结束后,分配的内存会自动释放。

关于局部变量是否产生符号

局部变量的作用域仅限于其所在的函数,因此在链接阶段不会产生符号。符号表中的符号通常是全局或静态变量、函数等,它们在编译和链接过程中需要被解析和分配内存。

4. C/C++的内存分配,详细说一下栈、堆、静态存储区?堆快还是栈快?

C/C++的内存分配主要包括以下几个部分:栈(Stack)、堆(Heap)和静态存储区(Static Storage Area)。这些部分的功能和特点如下:

  • 栈(Stack): 栈是一种用于存储局部变量和函数调用信息的内存区域。它是一种后进先出(LIFO)的数据结构,每当一个函数被调用时,都会在栈上创建一个栈帧(Stack Frame)。栈帧中包含了局部变量、函数参数以及其他与函数调用相关的信息。函数调用结束后,分配的内存会自动释放。栈的内存分配和释放速度非常快,但由于栈的大小有限,不能用于存储大量或运行时确定大小的数据。
  • 堆(Heap): 堆是一种用于动态分配内存的内存区域。在C++中,可以使用newdelete操作符(或new[]delete[]操作符)在堆上分配和释放内存。堆内存的分配和释放需要在运行时进行,速度相对较慢,且需要程序员手动管理内存的生命周期。不正确地管理堆内存可能导致内存泄漏或悬挂指针等问题。由于堆的大小远大于栈,可以用于存储大量或运行时确定大小的数据。
  • 静态存储区(Static Storage Area): 静态存储区是一种用于存储全局变量、静态变量和常量的内存区域。静态存储区的内存分配在编译时就确定,程序的整个生命周期内都存在,直到程序结束。静态存储区的内存分配速度快,但分配的内存大小在编译时就确定,不能在运行时改变。

关于堆和栈的速度

栈上的内存分配和释放速度要快于堆。这是因为栈内存分配和释放仅涉及对栈指针的移动,而堆内存分配和释放需要在运行时进行,涉及到内存管理的数据结构操作。此外,堆内存分配可能会导致内存碎片问题,降低内存利用率。

5. 内存的静态分配和动态分配的区别?

分配时间

  • 静态分配:在编译时确定内存的分配。静态变量、全局变量和常量都使用静态分配。此外,函数中的局部变量和函数参数通常使用栈进行分配,这也被视为静态分配,因为栈内存的分配和释放是在函数调用期间自动进行的。
  • 动态分配:在程序运行时分配内存。C++中,可以使用newdelete操作符(或new[]delete[]操作符)在堆上进行动态内存分配和释放。

生命周期

  • 静态分配:全局变量、静态变量和常量的生命周期在程序的整个执行期间。局部变量和函数参数的生命周期仅在函数调用期间。
  • 动态分配:动态分配的内存的生命周期取决于程序员何时释放它。它可以在程序的任何阶段创建和释放,但需要程序员手动管理内存的生命周期。

管理方式

  • 静态分配:静态分配的内存由编译器自动管理,程序员无需手动进行内存分配和释放。
  • 动态分配:动态分配的内存需要程序员手动进行分配和释放。如果不正确地管理动态分配的内存,可能导致内存泄漏或悬挂指针等问题。

使用场景

  • 静态分配:适用于程序运行期间固定大小的内存需求,例如全局变量、静态变量、常量以及局部变量和函数参数。
  • 动态分配:适用于程序运行时才能确定大小的内存需求,例如需要根据用户输入或运行时计算结果分配内存的情况。

6. malloc与free实现原理?

mallocfree 是 C 语言中用于在堆上分配和释放内存的标准库函数。

malloc

  • 内存分配:malloc 向操作系统请求内存,根据系统平台和库实现的不同,它可以使用 brk()mmap() 两种方式来从操作系统请求内存。小于128K用brk() 调整数据段的大小,以分配堆内存;大于128K用mmap() ,通过内存映射,在进程的虚拟地址空间中分配内存。
  • 内存管理:malloc 使用一种数据结构(通常是链表或树形结构)来管理已分配和未分配的内存块。当用户请求内存时,malloc 首先在内存管理数据结构中查找一个合适大小的空闲内存块。如果找到合适的内存块,malloc 会将其从空闲列表中移除并返回给用户。如果没有找到合适的内存块,malloc 会向操作系统请求更多的内存。

free

当调用 free 函数释放一块内存时,free 首先会根据之前 malloc 存储的元数据获取内存块的大小和位置。然后,free 会将这个内存块放回到空闲内存块列表中。

为了减少内存碎片,free 可能会合并相邻的空闲内存块。例如,如果一个被释放的内存块和它相邻的内存块都是空闲的,free 可以将它们合并成一个更大的空闲内存块。

mallocfree 仅分配和释放内存,而不对内存内容进行初始化。在 C++ 中,newdelete 操作符不仅负责内存分配和释放,还负责对象的构造和析构。

7. new与delete的实现原理?

newdelete 是 C++ 语言中用于在堆上分配和释放内存的操作符,它们不仅负责内存管理,还负责对象的构造和析构。

new

使用 new 操作符创建一个对象时,分为两步骤:

  • operator newnew 首先会调用底层的内存分配函数(例如,malloc)来分配所需的内存。这涉及到查找一个合适的空闲内存块并将其从空闲内存块列表中移除。需要注意的是,不同的编译器和平台可能使用不同的内存分配函数。如果分配失败会抛出bad_alloc异常
  • placement new:在分配的内存上调用对象的构造函数,以初始化对象的状态。

delete

使用 delete 操作符释放一个对象时,会进行以下步骤:

  • 对象析构:delete 首先调用对象的析构函数,以释放对象所持有的资源并清理对象的状态。
  • 内存释放:接着,delete 调用底层的内存释放函数(例如,free)将内存归还给操作系统。这涉及到将内存块放回空闲内存块列表,并可能合并相邻的空闲内存块以减少内存碎片。同样,不同的编译器和平台可能使用不同的内存释放函数。

8. delete是如何知道释放内存的大小?

与编译器和操作系统的实现有关。通常,编译器和内存管理子系统会在分配的内存块中存储一些元数据,以帮助跟踪内存的分配和释放。

当使用 new 操作符分配内存时,内存管理子系统会在实际分配的内存块中包含一些额外的信息,例如内存块的大小。这些信息通常存储在分配给对象的内存块之前的位置。当 delete 操作符被调用时,它会根据指针检索这些元数据,以确定要释放的内存块的大小。

9. malloc 和 new 有什么区别?

mallocnew 都用于在堆上分配内存。

可以调用构造函数与析构函数

类型安全

  • malloc:返回的指针类型为 void*,需要显式类型转换为适当的类型。
  • new:返回相应类型的指针,提供了类型安全。不需要显式类型转换。

内存分配

  • malloc:分配内存时,需要指定所需内存的字节数。它返回一个指向分配内存的指针,或者在无法分配内存时返回 NULL
  • new:分配内存时,无需指定字节数。它根据所需类型自动计算所需内存的大小,并返回相应类型的指针。如果无法分配内存,它会抛出 std::bad_alloc 异常(除非使用 nothrow 修饰符)。

10. malloc申请的存储空间能用delete释放吗?

使用 malloc 申请的存储空间不应使用 delete 来释放。同样,使用 new 分配的内存也不应使用 free 来释放。 mallocfree 是 C 语言的内存管理函数,而 newdelete 是 C++ 的内存管理操作符,内存管理策略不同。

newdelete 的内存管理除了分配和释放内存外,还涉及到对象的构造和析构。当你使用 new 创建对象时,它会在分配内存的同时调用对象的构造函数。而使用 delete 释放内存时,它会先调用对象的析构函数,然后释放内存。

如果使用 delete 来释放通过 malloc 分配的内存,可能会导致未定义行为,例如:

  • delete 可能尝试调用析构函数,而 malloc 分配的内存并没有调用构造函数。这可能导致程序错误,例如访问无效的资源。
  • mallocnew 可能使用不同的内存管理策略,导致 delete 无法正确释放 malloc 分配的内存。

11. malloc、realloc、calloc的区别?

mallocrealloccalloc 都是 C 语言中的内存分配函数,用于在堆上分配内存。

malloc

  • 功能:用于分配指定大小的内存。
  • 参数:接受一个参数,即所需内存的字节数。
  • 初始化:分配的内存不会被初始化,它包含的数据是未定义的。
  • 返回值:成功时返回指向分配内存的指针,失败时返回 NULL

calloc

  • 功能:用于分配指定数量的指定大小的内存块。
  • 参数:接受两个参数,一个是所需内存块的数量,另一个是每个内存块的字节数。
  • 初始化:分配的内存会被初始化为 0。
  • 返回值:成功时返回指向分配内存的指针,失败时返回 NULL

realloc

  • 功能:用于调整之前分配的内存块的大小。
  • 参数:接受两个参数,一个是指向之前分配的内存块的指针,另一个是新的内存大小(字节数)。
  • 初始化:如果内存块的大小增加,新增的内存部分不会被初始化,它包含的数据是未定义的。
  • 返回值:成功时返回指向新大小的内存的指针,失败时返回 NULL。在某些情况下,可能会返回与原始指针相同的值(例如,当内存大小未改变或内存已经足够大时)。如果返回的指针与原始指针不同,原始指针所指向的内存将被释放。

总结

  • malloc 是用于分配指定大小的内存,不进行初始化。
  • calloc 是用于分配指定数量的指定大小的内存块,并将内存初始化为 0。
  • realloc 是用于调整之前分配的内存块的大小,如果内存增加,新增部分不会被初始化。

12. 被free回收了的内存是否立刻还给操作系统?

当使用 free 函数回收内存时,这些内存是否立刻还给操作系统取决于实现和操作系统。C 库实现会在内部维护一个内存池,将回收的内存加入内存池以供将来分配。

在某些情况下,内存池可能会变得非常大,C 库实现可以选择将部分内存归还给操作系统。归还内存的策略取决于实现和操作系统,例如可能会基于内存使用量、空闲内存块的大小或空闲时间来决定。

free 函数主要目的是将内存归还给内部的内存池,以便在后续的内存分配请求(如 malloccallocrealloc)中重用。只有在特定条件下,内存才可能被归还给操作系统。

13. 深拷贝与浅拷贝?

浅拷贝: 浅拷贝是对对象的顶层结构进行复制,但不会复制对象内部的子对象或指针所引用的内存。换句话说,浅拷贝创建了一个新对象,这个新对象与原对象共享内部数据结构和引用。这可能会导致在一个对象上的更改影响另一个对象的情况。

举例来说,如果你有一个包含指针成员的对象,浅拷贝会复制指针本身,而不是指针所指向的内存。这意味着拷贝后的对象与原对象共享相同的指针指向的内存。

深拷贝: 深拷贝则是对对象的整个结构进行递归复制,包括子对象和指针所引用的内存。深拷贝会创建一个与原对象完全独立的新对象。由于深拷贝创建了原对象的完整副本,对一个对象的更改不会影响另一个对象。

以包含指针成员的对象为例,深拷贝会复制指针所指向的内存,然后在新对象中创建一个新的指针指向这块新的内存。这样,拷贝后的对象与原对象在内存上完全独立。

浅拷贝通常更快、占用更少内存,但可能导致意外的副作用,例如多个对象共享同一个内部数据结构。深拷贝虽然能够创建独立的对象副本,但可能更慢,占用更多内存。

你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.14.8