V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
albert0yyyy
V2EX  ›  C++

C++新手,求教为啥程序跑起来的内存越用越多

  •  
  •   albert0yyyy · 225 天前 · 3237 次点击
    这是一个创建于 225 天前的主题,其中的信息可能已经有所发展或是发生改变。

    感觉好像是哪个变量申请了内存没有释放,然后越用越多,大概 20-30 来个请求增加 0.1G 的内存,我没有 delete 一些指针的话,增加的更快。

    程序主要是通过 socket 通信,用 c++进行目标检测的这段代码。

    op 只学过 c++的基本语法,平时做深度学习 python 用的比较多,所以 c++这边可能会欠缺一点,只能说把一些 bug 修复了能跑起来的水平。其实也是拿着官方 demo 改的,参数直接构造了一个 fake command ,见笑了

    
    #include "jetson-utils/videoSource.h"
    #include "jetson-utils/videoOutput.h"
    #include "jetson-utils/imageLoader.h"
    #include "jetson-inference/detectNet.h"
    #include "jetson-inference/objectTracker.h"
    #include "cJSON.h"
    #include "jetson-utils/Socket.h"
    #include <signal.h>
    #include <vector>
    using namespace std;
    
    struct PredObj
    {
    	float x1;
    	float y1;
    	float x2;
    	float y2;
    	float score;
    	int classID;
    	float width;
    	float height;
    	const char *classDesc;
    };
    
    bool signal_recieved = false;
    
    void sig_handler(int signo)
    {
    	if (signo == SIGINT)
    	{
    		LogVerbose("received SIGINT\n");
    		signal_recieved = true;
    	}
    }
    
    int usage()
    {
    	printf("usage: detectnet [--help] [--network=NETWORK] [--threshold=THRESHOLD] ...\n");
    	printf("                 input [output]\n\n");
    	printf("Locate objects in a video/image stream using an object detection DNN.\n");
    	printf("See below for additional arguments that may not be shown above.\n\n");
    	printf("positional arguments:\n");
    	printf("    input           resource URI of input stream  (see videoSource below)\n");
    	printf("    output          resource URI of output stream (see videoOutput below)\n\n");
    
    	printf("%s", detectNet::Usage());
    	printf("%s", objectTracker::Usage());
    	printf("%s", videoSource::Usage());
    	printf("%s", videoOutput::Usage());
    	printf("%s", Log::Usage());
    
    	return 0;
    }
    
    int main(int argc, char **argv)
    {
    
    	/*
    	 * parse command line
    	 */
    	commandLine cmdLine(argc, argv);
    
    	if (cmdLine.GetFlag("help"))
    		return usage();
    
    	/*
    	 * create detection network
    	 */
    	detectNet *net = detectNet::Create(cmdLine);
    
    	if (!net)
    	{
    		LogError("detectnet:  failed to load detectNet model\n");
    		return 1;
    	}
    
    	// parse overlay flags
    	const uint32_t overlayFlags = detectNet::OverlayFlagsFromStr(cmdLine.GetString("overlay", "box,labels,conf"));
    
    	Socket *socket = Socket::Create(SOCKET_TCP);
    	if (!socket)
    	{
    		printf("failed to create socket\n");
    		return 0;
    	}
    
    	// bind the socket to a local port
    	if (!socket->Bind("0.0.0.0", 8899))
    	{
    		printf("failed to bind socket\n");
    		return 0;
    	}
    
    	if (!socket->Accept(0))
    	{
    		printf("failed to accept socket\n");
    		return 0;
    	}
    	printf("server is running\n");
    
    	if (cmdLine.GetFlag("help"))
    		return usage();
    
    	/*
    	 * attach signal handler
    	 */
    	if (signal(SIGINT, sig_handler) == SIG_ERR)
    		LogError("can't catch SIGINT\n");
    
    	/*
    	 * create input stream
    	 */
    
    	while (true)
    	{
    		// receive a message
    		uint8_t buffer[1024];
    		uint32_t remoteIP;
    		uint16_t remotePort;
    		size_t bytes = socket->Recieve(buffer, sizeof(buffer), &remoteIP, &remotePort);
    		if (bytes == 0)
    		{
    			printf("close client\n");
    			return 0;
    		}
    		else
    		{
    			const char *receiveData = NULL;
    			printf("received message: %s\n", buffer);
    			cJSON *cjson_receive = cJSON_Parse((char *)buffer);
    			receiveData = cJSON_Print(cjson_receive);
    			printf("%s\n", receiveData);
    			SAFE_DELETE(receiveData);
    
    			// cJSON *fStickLat = cJSON_GetObjectItem(cjson_receive, "fStickLat");
    			// double m_fStickLat = cJSON_GetNumberValue(fStickLat);
    			// printf("%.10f\n", m_fStickLat);
    			// printf("m_fStickLat: %.10f\n", m_fStickLat);
    			cJSON *fpath = cJSON_GetObjectItem(cjson_receive, "fpath");
    			char *m_fpath = cJSON_GetStringValue(fpath);
    			printf("m_fpath: %s\n", m_fpath);
    
    			// 检测到 socket 等于 shutdown ,关闭 socket
    			if (strcmp((char *)buffer, "shutdown") == 0)
    			{
    				printf("shutting down\n");
    				break;
    			}
    			char av1[] = "detectnet";
    			char av2[] = "--network=ssd-mobilenet-v2";
    			char av3[] = "./socket_example/test1.jpg";
    			int len = strlen(m_fpath);
    			char av3_2[len];
    			strcpy(av3_2, m_fpath);
    			// char *fake_argv[3] = {av1, av2, m_fpath};
    			char *fake_argv[3] = {av1, av2, av3_2};
    			commandLine cmdLine(3, fake_argv);
    			printf("t1\n");
    			cJSON_Delete(cjson_receive);
    			// cJSON_Delete(fpath);
    
    			// SAFE_DELETE(m_fpath);
    			printf("t2\n");
    
    			/*
    			 * create input stream
    			 */
    
    			videoSource *input = videoSource::Create(cmdLine, ARG_POSITION(0));
    
    			if (!input)
    			{
    				LogError("detectnet:  failed to create input stream\n");
    				// return 1;
    				continue;
    			}
    
    			/*
    			 * create output stream
    			 */
    			videoOutput *output = videoOutput::Create(cmdLine, ARG_POSITION(1));
    
    			if (!output)
    			{
    				LogError("detectnet:  failed to create output stream\n");
    				// return 1;
    				continue;
    			}
    			/*
    			 * processing loop
    			 */
    			// while( !signal_recieved )
    			//{
    			//  capture next image
    			uchar3 *image = NULL;
    			int status = 0;
    
    			input->Capture(&image, &status);
    
    			// if (!input->Capture(&image, &status))
    			// {
    			// 	// if( status == videoSource::TIMEOUT )
    			// 	// continue;
    
    			// 	// break; // EOS
    			// }
    
    			// detect objects in the frame
    			detectNet::Detection *detections = NULL;
    
    			const int numDetections = net->Detect(image, input->GetWidth(), input->GetHeight(), &detections, overlayFlags);
    			vector<PredObj> predObjList;
    
    			if (numDetections > 0)
    			{
    				LogVerbose("%i objects detected\n", numDetections);
    
    				for (int n = 0; n < numDetections; n++)
    				{
    					LogVerbose("\ndetected obj %i  class #%u (%s)  confidence=%f\n", n, detections[n].ClassID, net->GetClassDesc(detections[n].ClassID), detections[n].Confidence);
    					LogVerbose("bounding box %i  (%.2f, %.2f)  (%.2f, %.2f)  w=%.2f  h=%.2f\n", n, detections[n].Left, detections[n].Top, detections[n].Right, detections[n].Bottom, detections[n].Width(), detections[n].Height());
    
    					if (detections[n].TrackID >= 0) // is this a tracked object?
    						LogVerbose("tracking  ID %i  status=%i  frames=%i  lost=%i\n", detections[n].TrackID, detections[n].TrackStatus, detections[n].TrackFrames, detections[n].TrackLost);
    
    					PredObj predObj;
    					predObj.x1 = detections[n].Left;
    					predObj.y1 = detections[n].Top;
    					predObj.x2 = detections[n].Right;
    					predObj.y2 = detections[n].Bottom;
    					predObj.score = detections[n].Confidence;
    					predObj.classID = detections[n].ClassID;
    					predObj.width = detections[n].Width();
    					predObj.height = detections[n].Height();
    					predObj.classDesc = net->GetClassDesc(detections[n].ClassID);
    					predObjList.push_back(predObj);
    				}
    			}
    
    			// predObjList to json
    			cJSON *cjson_send = cJSON_CreateObject();
    			cJSON *cjson_array = cJSON_CreateArray();
    			for (int i = 0; i < predObjList.size(); i++)
    			{
    				cJSON *cjson_obj = cJSON_CreateObject();
    				cJSON_AddNumberToObject(cjson_obj, "x1", predObjList[i].x1);
    				cJSON_AddNumberToObject(cjson_obj, "y1", predObjList[i].y1);
    				cJSON_AddNumberToObject(cjson_obj, "x2", predObjList[i].x2);
    				cJSON_AddNumberToObject(cjson_obj, "y2", predObjList[i].y2);
    				cJSON_AddNumberToObject(cjson_obj, "score", predObjList[i].score);
    				cJSON_AddNumberToObject(cjson_obj, "classID", predObjList[i].classID);
    				cJSON_AddNumberToObject(cjson_obj, "width", predObjList[i].width);
    				cJSON_AddNumberToObject(cjson_obj, "height", predObjList[i].height);
    				cJSON_AddStringToObject(cjson_obj, "classDesc", predObjList[i].classDesc);
    				cJSON_AddItemToArray(cjson_array, cjson_obj);
    			}
    			cJSON_AddItemToObject(cjson_send, "predObjList", cjson_array);
    			char *json_str = cJSON_Print(cjson_send);
    
    			if (!socket->Send((void *)json_str, strlen(json_str), remoteIP, remotePort))
    			{
    				// printf("failed to send message\n");
    				printf("client is disconnected\n");
    				break;
    			}
    			printf("t3\n");
    			//cJSON_Delete(cjson_send);
    			cJSON_Delete(cjson_array);
    			SAFE_DELETE(json_str);
    
    			// render outputs
    			if (false && output != NULL)
    			{
    				output->Render(image, input->GetWidth(), input->GetHeight());
    
    				// update the status bar
    				char str[256];
    				sprintf(str, "TensorRT %i.%i.%i | %s | Network %.0f FPS", NV_TENSORRT_MAJOR, NV_TENSORRT_MINOR, NV_TENSORRT_PATCH, precisionTypeToStr(net->GetPrecision()), net->GetNetworkFPS());
    				output->SetStatus(str);
    
    				// check if the user quit
    				// if( !output->IsStreaming() )
    				// break;
    			}
    
    			// print out timing info
    			net->PrintProfilerTimes();
    			printf("t4\n");
    
    			SAFE_DELETE(input);
    			SAFE_DELETE(output);
    			// SAFE_DELETE(image);
    
    			//}
    		}
    	}
    	/*
    	 * destroy resources
    	 */
    	LogVerbose("detectnet:  shutting down...\n");
    
    	SAFE_DELETE(net);
    
    	LogVerbose("detectnet:  shutdown complete.\n");
    	return 0;
    }
    
    
    17 条回复    2024-05-17 19:06:45 +08:00
    shuax
        1
    shuax  
       225 天前   ❤️ 1
    你都知道你写的是 C++了,多用 RAII 自动释放啊,手动释放太难了。
    YsHaNg
        2
    YsHaNg  
       225 天前
    TL;DR 扔给 GPT 了
    Yes, there are several memory leaks in the provided code. Let's go through them:

    1. In the loop where you receive messages via the socket, you allocate memory for `receiveData` using `cJSON_Print`, but you don't free it. You should free it after you're done using it.

    2. Similarly, you create a `cJSON` object using `cJSON_Parse`, but you don't free it using `cJSON_Delete`.

    3. You allocate memory for `json_str` using `cJSON_Print`, but you don't free it after sending it over the socket.

    4. You allocate memory for `input` and `output` inside the loop, but you don't free them before the next iteration of the loop. This can lead to memory leaks if `input` or `output` fail to initialize.

    To fix these memory leaks:

    - Free `receiveData` after its use with `SAFE_DELETE(receiveData)`.

    - Delete the `cjson_receive` object using `cJSON_Delete` after its use.

    - Free `json_str` after sending it over the socket.

    - Delete `input` and `output` objects at the end of each iteration of the loop before reinitializing them.

    Here's how you can do it:

    ```cpp
    SAFE_DELETE(receiveData);
    cJSON_Delete(cjson_receive);
    SAFE_DELETE(json_str);
    SAFE_DELETE(input);
    SAFE_DELETE(output);
    ```

    Place these lines at appropriate locations in your code to ensure memory allocated dynamically is properly deallocated.
    albert0yyyy
        3
    albert0yyyy  
    OP
       225 天前
    @YsHaNg gpt 我问了,好像并不能解决.

    有的指针释放了会报错提示已经释放过了,不能再次释放了。所以我就注释了一些会报错的释放操作
    albert0yyyy
        4
    albert0yyyy  
    OP
       225 天前
    @shuax 我去看看 RAII ,感谢
    YsHaNg
        5
    YsHaNg  
       225 天前   ❤️ 1
    @albert0yyyy RAII+smart ptr 多用 auto 会让生活好很多
    albert0yyyy
        6
    albert0yyyy  
    OP
       225 天前
    @YsHaNg #5 感谢,我去看看。
    watzds
        7
    watzds  
       225 天前
    内存越用越多也正常啊,看能增加到多少
    hello2090
        8
    hello2090  
       225 天前
    你实在调不出来的话,code 一部分一部分的 comment 掉试呗
    bfjm
        9
    bfjm  
       225 天前
    1. 可以使用 valgrind 看看哪里会有内存泄漏
    2. 看这个 cJSON 的情况是分配了内存,需要手动调用他的内存分配函数,你可以使用 RAII 封装一下
    类似这种
    ```c++
    class smart_ptr
    {
    smart_ptr(char *buffer)
    {

    }

    ~smart_ptr()
    {

    }

    };


    ```
    bfjm
        10
    bfjm  
       225 天前
    class smart_ptr
    {
    smart_ptr(char *buffer)
    {
    cjson = cJSON_Parse((char *)buffer)
    }

    ~smart_ptr()
    {
    cJSON_Delete(cjson_receive);
    }
    cJSON *cjson
    };

    @bfjm
    bfjm
        11
    bfjm  
       225 天前
    另外你先得了解清除,这里面谁分配了内存,(谁分配谁释放),分配的是堆内存还是栈内存,栈内存不用手动释放,堆内存需要手动释放
    bfjm
        12
    bfjm  
       225 天前
    我不太确定 cJSON *cjson_obj = cJSON_CreateObject(); 这里分配内存没有,这里会比较可疑
    sidyhe
        13
    sidyhe  
       225 天前
    查代码比较难的话, 用 tcmalloc 检测泄露, 它会告诉你哪里的内存没有释放
    cnbatch
        14
    cnbatch  
       225 天前
    单凭肉眼观察发现以下几个内存泄漏点,可能不完整:

    第一个:
    cJSON *cjson_receive = cJSON_Parse((char *)buffer);
    一旦 if (strcmp((char *)buffer, "shutdown") == 0)这里 break 掉,后面的 cJSON_Delete(cjson_receive)就无法执行,内存就这样泄露掉了

    第二个:
    videoSource::Create()调用了两次,第一次问题不大,第二次处理不妥
    原因同第一次。如果 input 创建成功但 output 创建失败,那么随后的 continue 会导致 input 指针内存泄漏。

    第三个:
    if (!socket->Send((void *)json_str, strlen(json_str), remoteIP, remotePort))
    同理,break 之前未释放内存

    建议上述内存分配都用智能指针包起来,必要时传递析构用的释放函数,就像这样:
    std::unique_ptr<cJSON, decltype(cJSON_Delete)> cjson_receive{ cJSON_Parse((char *)buffer), cJSON_Delete };
    yolee599
        15
    yolee599  
       225 天前
    我的看法,基本和 14 楼的一致,补充一点 socket 还有很多地方没处理好,比如:
    1. if (!socket) 的时候应该释放 net 再 return 。
    2. if (!socket->Bind("0.0.0.0", 8899)) 的时候应该同时释放 net 和 socket 再 return 。
    3. 下面所有的 return 同理。
    4. 在程序结束的时候 socket 也应该关闭和释放。

    input 和 output ,如果 input 分配成功了,但是 output 分配失败了,在 continue 之前要先释放掉 input 。
    其实主要看这几个关键字是否正确处理了之前分配的内存就行:return/break/continue/goto 。
    albert0yyyy
        16
    albert0yyyy  
    OP
       225 天前
    @watzds
    @hello2090
    @bfjm #9

    @sidyhe
    @cnbatch
    @yolee599


    谢谢各位哥哥们,学到了很多 C++的技巧,我去改改。回头给你们反馈
    nooneanyone
        17
    nooneanyone  
       224 天前
    你如果不差共享指针这点内存和调用性能,就用 shared_ptr make_shared 吧。不要自己 new delete
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2846 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 08:52 · PVG 16:52 · LAX 00:52 · JFK 03:52
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.