在Qt中使用asan检测内存泄漏(一)

在前面的第一次使用address sanitizer记录_g++ -fsanitize=address-CSDN博客,我介绍了如何编译一个支持asan的C++程序,并利用它检测内存非法访问。

本篇博文继续介绍如何在Qt程序中集成asan,并检测内存泄漏。

目录

[1 写一个pri文件,包含asan支持](#1 写一个pri文件,包含asan支持)

[2 在pro文件里通过include(asan.pri)包含asan支持](#2 在pro文件里通过include(asan.pri)包含asan支持)

[3 故意在main.cpp里留下一个内存泄漏的点](#3 故意在main.cpp里留下一个内存泄漏的点)

[4 编译,运行,观察结果](#4 编译,运行,观察结果)

[5 解释](#5 解释)

[6 补记](#6 补记)


1 写一个pri文件,包含asan支持

当然,你也可以把下面的内容直接写道pro文件里,但是考虑到真实的项目往往包含多个so文件。假如你想保证每个so文件的内存泄漏都被检测,那就要在每个pro文件里都写下面的内容。这就很繁琐,容易出错。不如把asan的配置写在一个pri文件里,哪个pro文件需要,就包含这个pri。

bash 复制代码
CONFIG += sanitizer sanitize_address

QMAKE_CXXFLAGS += -fno-omit-frame-pointer
QMAKE_CFLAGS += -fno-omit-frame-pointer

2 在pro文件里通过include(asan.pri)包含asan支持

bash 复制代码
#-------------------------------------------------
#
# Project created by QtCreator 2026-10-20T21:20:51
#
#-------------------------------------------------

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = ASAN
TEMPLATE = app

include(asan.pri)

INCLUDEPATH += /opt/gdal/include
LIBS += -L/opt/gdal/lib64 -lgdal
# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0


SOURCES += \
        main.cpp

HEADERS +=

3 故意在main.cpp里留下一个内存泄漏的点

main函数中注释掉delete \[\] pImgData一句。

cpp 复制代码
/*#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}*/

#include <gdal_priv.h>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <ctype.h>

char * my_strlwr(char * str)
{
    char * p = str;
    while(*p)
    {
        unsigned char uc = (unsigned char)*p;
        uc = tolower(uc);
        *p = (char)uc;
        p++;
    }

    return str;
}


bool bReadImageMultiBand(unsigned char * & pImgData, int & iWidth, int & iHeight, int & iChannel, char * strPath);
bool bWriteImageData(char *, unsigned char *, int, int, int, int);
void vFindImageTypeGDAL(char *, char *);

int main(void)
{
    GDALAllRegister();
    int iWidth, iHeight, iChannel;
    unsigned char * pImgData;
    if(bReadImageMultiBand(pImgData, iWidth, iHeight, iChannel, "earthRGBRGB.tif"))
    {
        char arr[64] = {0};
        memcpy(arr, "earth.bmp", strlen("earth.bmp"));
        bWriteImageData(arr, pImgData, iWidth, iHeight, iChannel, iChannel);
        //delete [] pImgData;
    }


    return 0;
}

bool bReadImageMultiBand(unsigned char * & pImgData, int & iWidth, int & iHeight, int & iChannel, char * strPath)
{
    GDALDataset * pDataSet = nullptr;
    pDataSet = (GDALDataset *)GDALOpen(strPath, GA_ReadOnly);
    if(pDataSet == nullptr)
    {
        GDALClose(pDataSet);
        return false;
    }

    GDALDataType dt = pDataSet->GetRasterBand(1)->GetRasterDataType();
    iWidth = pDataSet->GetRasterXSize();
    iHeight = pDataSet->GetRasterYSize();
    iChannel = std::min(pDataSet->GetRasterCount(), 3);
    int arrBandMap[] = {1,2,3};
    pImgData = new unsigned char[iWidth * iHeight * iChannel];
    pDataSet->RasterIO(GF_Read, 0,0, iWidth, iHeight, pImgData, iWidth, iHeight, dt, iChannel, arrBandMap, 0,0,0);
    GDALClose(pDataSet);

    return true;
}

bool bWriteImageData(char * strDestPath, unsigned char * pData, int iWidth, int iHeight, int iSrcChannel, int iDestChannel)
{
    char arrType[1024] = {0};
    vFindImageTypeGDAL(strDestPath, arrType);

    if(arrType)
    {
        std::fstream file;
        file.open(strDestPath, std::ios_base::out);
        file.close();
        GDALDriver * pMemDriver = GetGDALDriverManager()->GetDriverByName("MEM");
        if(pMemDriver == nullptr)
            return false;

        GDALDataset * pMemDataSet = pMemDriver->Create("",iWidth,iHeight,iDestChannel,GDT_Byte,NULL);
        GDALRasterBand *pBand = NULL;
        int arrBandMap[3] = { 1, 2, 3 };
        pMemDataSet->RasterIO(GF_Write,
            0,
            0,
            iWidth,
            iHeight,
            pData,
            iWidth,
            iHeight,
            GDT_Byte,
            iDestChannel,
            arrBandMap,
            0,0,0);

        GDALDriver *pDstDriver = NULL;
        pDstDriver = (GDALDriver *)GDALGetDriverByName(arrType);
        if (pDstDriver == NULL)
        {
            GDALClose(pMemDataSet);

            return false;
        }

        GDALDataset * pDataSet = pDstDriver->CreateCopy(strDestPath,pMemDataSet,FALSE, NULL, NULL, NULL);
        if (pDataSet == NULL)
        {
            GDALClose(pMemDataSet);

            return false;
        }

        GDALClose(pDataSet);
        GDALClose(pMemDataSet);

        return true;
    }
    else
    {
        return false;
    }
}

void vFindImageTypeGDAL(char * pDstImgFileName, char * pDst)
{
    char * dstExtension = my_strlwr(strrchr(pDstImgFileName, '.') + 1);

    if (0 == strcmp(dstExtension, "bmp"))
        sprintf(pDst, "%s", "BMP");
    else if (0 == strcmp(dstExtension, "jpg"))
        sprintf(pDst, "%s", "JPEG");
    else if (0 == strcmp(dstExtension, "png"))
        sprintf(pDst, "%s", "PNG");
    else if (0 == strcmp(dstExtension, "tif"))
        sprintf(pDst, "%s", "GTiff");
    else if (0 == strcmp(dstExtension, "gif"))
        sprintf(pDst, "%s", "GIF");
}

4 编译,运行,观察结果

编译后,输入:

bash 复制代码
ASAN_OPTIONS=detect_leaks=1 ./ASAN

输出:

5 解释

在main函数if分支的最后,注释掉了delete \[\] pImgData一句。这使得原来main.cpp70行的

pImgData = new unsigned chariWidth \* iHeight \* iChannel;

申请的内存不能释放,故给出内存泄漏报告。

6 补记

本项目的源码已经上传至【免费】Qt集成asan的示例资源-CSDN下载

读者可能已经注意到了,这个项目还依赖gdal。gdal在中标麒麟的安装,见我以前的博客CMake编译gdal3.5.3_gdal 3.5.-CSDN博客

相关推荐
前行中的小猪1 小时前
Qt 5 踩坑实录:多显示器跨屏拖拽导致窗口异常放大与 setGeometry 报错的深度解析与根治
qt·多显示器·高分辨率·多屏幕
第404号工位1 小时前
“已彻底删除”的照片与文件到底去哪了?我用底层扇区扫描,扒出手机和电脑的幽灵数据
linux·python·架构
硅基手札2 小时前
SPI技术系列 12 · SPI 设备驱动实战
linux·驱动开发·单片机·嵌入式硬件·mcu·性能优化·系统架构
不悔哥2 小时前
ESP32上跑 Linux: 乐鑫给 ESP32-S31开放了官方 BSP
linux·嵌入式硬件·开源
syagain_zsx2 小时前
WebSocket 教程:websocketpp 写 echo 服务端
linux·网络·c++·websocket·网络协议·工具库
无足鸟ICT2 小时前
【RHCA+】_etc_yum.repos.d_文件名.repo
linux·运维·centos
骑着蜗牛撵大象3272 小时前
字节序、TLV 到 netlink:Linux 网络消息的编解码全链路
linux·网络·php·etl·序列化·字节序·tlv
tianyuanwo3 小时前
RPM Spec中Name: %{?scl_prefix}xxx中增加宏定义的设计原理
linux·服务器·rpm·scl
想做小南娘,发现自己是女生喵3 小时前
QT项目实战第一周(嵌入式)
开发语言·qt