019-C++跨平台开发内存检测

《C++文章汇总》
上一篇介绍了《018-智能指针》,本文介绍实际开发中利用Xcode工具对内存进行检测。

在音视频开发中,不可避免要使用C++,需要我们自己管理内存,不像OC可通过自动释放池autoreleasepool管理内存,如何知道我们写的C++代码是否有内存忘记释放,内存泄露,内存破坏呢?可充分利用Xcode检测调试工具进行检测调试

1.开发中遇到的问题

  • 需求
图片.png

图片.png
  • 现实开发中
图片.png

图片.png

图片.png
  • 内存问题


    图片.png

I.内存破坏

class Cat{
public:
    char name[4];
    int age;
    void show(){
        std::cout << "cat name:" << name << ", age:" << age << std::endl;
    }
};

class Dog{
public:
    char tag[4];
    char name[8];
    int year;
    void show(){
        std::cout << "dog name:" << name << ",age:" << year << std::endl;
    }
    void setName(char *name_){
        strcpy(name, name_);
    }
};

int main(){
    Cat cat = {
        .name = "Tom",
        .age = 6,
    };
    Dog *dog = (Dog *)&cat;
    dog->setName((char *)"d");
    cat.show();
    return 0;
}
//输出
cat name:Tom, age:100

此时猫的age变为100,因为猫对象的内存地址被破坏了,dog指向了猫的内存地址,并将这块内存地址的第五个字节赋值为"d"


图片.png

II.内存泄露

现象:内存不断上涨或周期性上涨
模块析构后内存没有下降


图片.png

处理方法:
A.开启内存日志
B.使用内存泄漏工具发现泄漏内存
C.通过内存malloc日志找到创建的调用栈
D.跟踪内存使用情况


图片.png
#include 
#include 
#include 
#include 
class MyFancyObject{
private:
    size_t buffer_size_;
    char *buffer_;
public:
    MyFancyObject():buffer_size_(1024),buffer_((char*)malloc(buffer_size_)){};
    void say(){
        printf("buffer: %p,size:%ld\n",buffer_,(long)buffer_size_);
    }
};
void TaskOne(){
    auto object = MyFancyObject();
    object.say();
}

int main(int argc,const char *argv[]){
    std::cout << "App Started\n";
    auto task1 = std::thread([]{
        for (;;) {
            TaskOne();
            usleep(100);
        }
    });
    task1.join();
    for (;;);
    return 0;
}

发现内存一直缓慢增加,通过Xcode查看堆栈信息

一、开启Allocation Log

  1. cmd+shift+< 编辑 Schema
  2. 打开 Malloc Stack Logging


    图片.png

二、打开Memory Graph


图片.png

Memory Graph 使用方法
1.点过滤按钮查看问题内存列表
2.点击问题项查看
3.右侧显示创建时的调用栈


图片.png

图片.png

图片.png

MyFancyObject对象创建时申请了一块buffer_size_大小的内存空间,没有析构函数去释放
给MyFancyObject增加析构函数去释放即可

class MyFancyObject{
private:
    size_t buffer_size_;
    char *buffer_;
public:
    MyFancyObject():buffer_size_(1024),buffer_((char*)malloc(buffer_size_)){};
    void say(){
        printf("buffer: %p,size:%ld\n",buffer_,(long)buffer_size_);
    }
    ~MyFancyObject(){
        free(buffer_);
    }
};
图片.png

内存占比很平稳,一条平行线,没有内存泄露

III.内存忘记释放

现象:在模块重复使用过程中内存阶梯式上涨
模块析构后内存没有下降


图片.png

处理思路:使用功能后看残存的内存
开启 Profile 打开 Instrument
长按运行按钮选 Profile 或 Cmd + I


图片.png

选则 Allocations
点击 Choose
图片.png

点击录制按钮运行


图片.png

图片.png

图片.png

重复执行任务
如进出同一个界面,
重复执行同一个任务等
(示例代码中为重复玩游戏)
在每个周期前点击
“Mark Generation” 按钮
图片.png

展开后可以看snapshot中
当前仍然未释放的对象

右侧查看创建调用栈


2.gif

图片.png

图片.png

图片.png

game对象一直存在于队列games中未被释放,需在上报后将game对象从队列中移除games.pop_back();
class ScoreUploader{
public:
    ScoreUploader(){
        //上报线程,轮询上报队列上报
        _thread = std::thread([this]{
            for (;;) {
                {
                    std::lock_guard l(mu);
                    if (!games.empty()) {
                        //Upload last game result
                        //do_report(games.front())
                        games.pop_back();
                        sleep(2);
                    }
                }
                sleep(1);
            }
        });
    }
    ~ScoreUploader(){
        _thread.join();
    }
    void Upload(std::unique_ptr game){
        //将完成的游戏加入上报队列
        std::lock_guard l(mu);
        games.push_back(std::move(game));
    }
private:
    std::thread _thread;
    std::mutex mu;
    std::vector> games;
};
图片.png

IV.内存被破坏

现象1.偶现异常Crash

图片.png

现象2.奇怪的输出

如出现了不在上下文的字符串、
花屏、数值异常等


图片.png

这里随机打印四个字符串,但有奇怪的东西混进来了
一、开启Address Sanitizer,关闭Allocation Log(两者不能同时存在,都是hook malloc函数)1. cmd+shift+< 编辑 Schema

  1. 打开 Address Sanitizer


    图片.png

    运行后会挂在内存破坏的指令上


    图片.png

    控制台会输出原因及详细信息
    图片.png

    Xcode 会根据错误信息作展示


    图片.png

    如此处会展示访问的内存的
    创建调用栈
    析构调用栈
    图片.png

二、为什么会有XXXXXXX的打印出现?

图片.png

作用域{}结束后MyFancyObject对象被释放,buffer_指针指向的区域被释放,buffer_指针仍然指向这块区域,变为野指针,这块区域中的内容等着被覆盖,此时obj被干掉,指针还在
某一时刻,TwoTask刚刚申请一块内存区域给obj,并分配区域给buffer_指向,写入了四个随机字符串中的一个,此时调用了析构函数,buffer_变成了野指针,此时这块区域又被分配给了ThreeTask中的obj对象的成员变量buffer_指向,正好写入了"XXXXXXXXXX",覆盖了随机字符串,程序在TwoTask执行writer->present();打印"XXXXXXXXXX"
三、为什么会Crash?
在作用域{}中,obj对象MyFancyObject被创建,构造函数中会申请1024个字节的buffer_内存空间,在作用域{}结束,obj对象被释放,buffer_指向的1024个字节也被释放
Writer对象的构造方法中会对obj对象有一个引用,有个成员变量obj指向MyFancyObject对象obj的内存空间,此时obj对象已经被释放了,Writer对象中的指针obj指向的是一块被释放的区域,是一个野指针
这个时候再往buffer_指向的区域去写数据,会报错,工具能帮助定位偶现的crash,基本可以让一个偶现的问题变成必现的问题
四、工具的原理:在创建对象前或后加一些redzone标记,8个字节内存会增加标记位,如果8字节内存被析构掉会把标记位置为负数,编译器加一些代码,访问内存的时候去检查,这块内存是否被释放掉了
图片.png

图片.png

图片.png

Demo地址

你可能感兴趣的:(019-C++跨平台开发内存检测)