2024/02/21

本文详细解释了C++中的new和malloc内存管理区别,包括它们的本质属性、错误处理、类对象分配、类型安全以及野指针问题。同时介绍了程序编译的预处理、编译、汇编和链接四个阶段,以及动态库、静态库和运行时库的概念及其应用。
摘要由CSDN通过智能技术生成

目录

1. new和malloc

1.1 new

1.1.1 new和delete

1.2 new和malloc区别

1.2.1 本质属性

1.2.2 错误处理

1.2.3 对于类的分配

1.2.4 类型安全

1.2.5 野指针问题

2. 程序编译四个阶段


1. new和malloc

堆区数据由程序员开辟,需要手动释放

1.1 new

1.1.1 new和delete
int* func()
{
    int* p = new int(10);
    return p;
}

void test()
{
    int* ptr = func();
    cout<< *ptr <<endl;
    delete ptr;
}

开辟&释放数组:

int* func()
{
    int* p = new int[10];    //10个int元素的数组
    return p;                //返回首地址
}

void test()
{
    int* arr = func();
    for(int i = 0; i < 10; i++)
    {
        arr[i] = i + 100;
    }
    delete[] arr;            //释放数组
}

1.2 new和malloc区别

1.2.1 本质属性

new是一个C++中的运算符(不需要引入头文件),而malloc是一个函数(需要引入头文件)

1.2.2 错误处理

new内存分配失败抛出bac_alloc异常,malloc内存分配失败返回空指针。所以需要有不同的判断机制。

对于new:

try {
    int* p = new int[SIZE];
    // 其它代码
} catch ( const bad_alloc& e ) {
    return -1;
}

对于malloc:

int* p = (int*)malloc(sizeof(int));
if ( p == 0 ) // 检查 p 是否空指针
    return -1;
...
1.2.3 对于类的分配

对于类,new 不止是分配内存,而且会调用类的构造函数,同理delete会调用类的析构函数。而malloc则只分配内存,不会进行初始化类成员的工作,同样free也不会调用析构函数。

1.2.4 类型安全

new是类型安全的,如果一个int类型指针指向float会报错。

malloc类型不安全,一个int*指针可以指向double类型大小的内存空间而不报错。

1.2.5 野指针问题
delete/free命令指示释放了那个指针原本所指的那部分内存而已。被delete/free后的指针p的值(地址值)并非就是NULL,而是随机值。
所以,正确做法是在delete/free之后,让该指针等于null。
void test()
{
    int* ptr = new int(10);
    cout<< *ptr <<endl;
    delete ptr;
    ptr = nullptr;    //ptr = NULL;    
}

2. 程序编译四个阶段

2.1 预处理阶段。

gcc -E helloworld.c -o helloworld.i

预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。比如hello.c中第一行的#include<stdio.h>命令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入程序文本中,结果就得到了另一个C程序,通常是以.i作为文件扩展名。

2.2 编译阶段。

编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。汇编语言程序中的每条语句都以一种标准的文本格式确切的描述了一条低级机器语言指令。在这个阶段中,gcc首先要检查代码的规范性、是否有语法错误等,以确定代码实际要做的工作,在检查无误后,gcc把代码编译成汇编代码。

gcc -S helloworld.i -o helloworld.s

2.3 汇编阶段。

汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种可重定位目标程序的格式,并将结果保存在目标文件hello.o中。hello.o文件是一个二进制文件,它的字节编码是机器语言指令而不是字符,如果我们在文本文件中打开hello.o文件,看到的将是一堆乱码。

gcc -c helloworld.s -o helloworld.o

2.4链接阶段。

链接器(ld)负责处理合并目标代码,生成一个可执行目标文件,可以被加载到内存中,由系统执行。

这里涉及到一个重要的概念:函数库。
读者可以重新查看这个小程序,在这个程序中并没有定义“printf”的函数实现,且在预编译中包含进去的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么是在哪里实现的“printf”函数的呢?答案是:系统把这些函数实现都做到了名为libc.so.6的库文件中去了,在没有特别指定时,gcc会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用。
函数库一般分为静态库和动态库两种。静态库是指在编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不需要库文件了,其后缀一般为“.a”。动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时链接文件加载库,这样就可以节省系统的开销,动态库一般后缀名为“.so”,如前面所述的libc.so.6就是动态库。gcc在编译时默认使用动态库。

gcc helloworld.o -o helloworld
./helloworld

3. 动态库、静态库、运行时库

静态库/动态库/运行时库/编译时库:

一般我们编译时:g++ hello.cpp main.cpp -o main

但想保证hello.cpp机密性:创建库

静态库:Linux下.a,Windows下.lib

编译二进制文件:g++ -c hello.cpp -o hello.o

打包成库文件:ar -cr -o -libhello.a hello.o

之后就不需要hello.cpp了

Main.cpp中

#include “hello.h”

int main(){ printhello(); }

编译时:g++ main.cpp -lhello -L ./ -o main.exe

之后,就算把libhello.a也删掉,main.exe一样可以运行,说明程序运行是不依赖于静态库的

动态库:Linux下.so,Windows下.dll

g++ hello.cpp -shared -o libhello.dll

编译时:g++ main.cpp -lhello -L ./ -o main.exe

之后,把libhello.a也删掉,main.exe不可以运行,说明程序运行是依赖于动态库的。编译的时候并没有被编译进目标代码中,而是程序执行到相关函数时才调用库中对应的函数。

静态库用于比较小的程序,缺点是若静态库改变则程序需重新编译,可执行程序体积在生成后会比较大。优点是编译后的执行程序无需外部的函数库支持(删除静态库对可执行程序无影响)

动态库虽然需要分发(安装包),但是更新方便。比如之后改变hello.cpp的函数,直接g++ hello.cpp -shared -o libhello.dll之后运行main.exe就行,不用在编译一次main了。不同的应用程序若调用相同的库,那么内存里只需一份该共享库。缺点是程序运行时才被载入,故运行时需动态库存在。

运行时库:Runtime Library就是运行时库,也简称CRT(C Run Time Library)。是程序在运行时所需要的库文件,通常运行时库是以Lib或Dll形式提供的。确切地说运行时库指的就是对这些底层的基础功能实现的动态库(Dll),运行时库和普通的Dll一样,只有程序用到了它才会被加载,没有程序使用的时候不会驻留内存的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值