Linux C определить что у программы нет утечки памяти

Приветствую!

Нарисовал программулину, как быть увереным что в программе все дескрипторы выделения памяти корректно освобождаются и нет утечек?
Можно ли следить за объемом выделяемой памяти в htop и если цифры не увеличиваются - быть уверенным что все хорошо?

Для начала запустите ее от пользователя, а не от root.

запустил, наблюдаю…

А что она такого делат, чтобы утекала память?

Делает то она не особо сложные вещи, берет с сервера через zabbix api проблемы и отображает их на экране.

Вопрос насколько корректно я использовал malloc/realloc/free чтоб программа могла стабильно работать максимально возможное время не зависнув и не завесив всю ОС.

С объектами какого размера ты работаешь, если тебе на Оранж Пай приходится реально пользоваться динамической памятью?
Но если беспокоишься, то просто выведи в лог занятую память и проследи сутки.
Но лучший способ не переживать за кучу - ей не пользоваться :wink: Есть же стек.

А так есть приемы “для джунов”, чтобы проверять себя. Например при вызове malloc сперва проверять, что указатель не определен. И так далее. самые простые приемы себя защитить от своих ошибок. Есть целые библиотеки написанные для этого. Но в маленьком коде это скорее всего не нужно

  • объекты строковые, десятки и сотни байт, но их количество постоянно разное.

Т е я беру json, разбираю его (с помощью готовой библиотеки) на списки, потом их уже собираю в кучу.

Понятно, спасибо!
Буду проверять/тестировать.

так то код примитивный:

// list zabbix -> l.h

#pragma once

#include <stdlib.h>

struct zabbixList {
	char ** headList;
	size_t countStrings;
	size_t getPos;
};

void listInit(struct zabbixList * inList);
void listAddString(const char * inStr, struct zabbixList * inList);
void listTest(void);
char * listGetFirst(struct zabbixList * inList);
char * listGetNext(struct zabbixList * inList);
void listDelete(struct zabbixList * inList);

struct resultItem {
	size_t severityProblem;
	char * hostName;
	char * problemName;
	struct resultItem * prevItem;
	struct resultItem * nextItem;
};

struct resultList {
	struct resultItem ** headResult;
	size_t countResults;
};

void resultListInit(struct resultList * inResult);
void resultAddItem(const size_t inSev, const char * inHost, const char * inProblem, struct resultList * inResult);
void resultListDelete(struct resultList * inResult);
void resultToConsoleList(struct resultList * inResult);
void resultOrderBySev(struct resultList * inResult);

// zabbix list -> l.c

#include "l.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void resultOrderBySev(struct resultList * inResult) {
	if (inResult->countResults > 1) {
		for(size_t i = 0; i < (inResult->countResults - 1); ++i) {
			for(size_t j = (i+1); j < inResult->countResults; ++j) {
				if (inResult->headResult[j]->severityProblem > inResult->headResult[i]->severityProblem) {
					size_t sp = inResult->headResult[i]->severityProblem;
					char * hn = inResult->headResult[i]->hostName;
					char * pn = inResult->headResult[i]->problemName;
					inResult->headResult[i]->severityProblem = inResult->headResult[j]->severityProblem;
					inResult->headResult[i]->hostName = inResult->headResult[j]->hostName;
					inResult->headResult[i]->problemName = inResult->headResult[j]->problemName;
					inResult->headResult[j]->severityProblem = sp;
					inResult->headResult[j]->hostName = hn;
					inResult->headResult[j]->problemName = pn;
				}
			}
		}
	}
}

void resultListInit(struct resultList * inResult) {
	inResult->headResult = NULL;
	inResult->countResults = 0;
}

void resultToConsoleList(struct resultList * inResult) {
	for(size_t i = 0; i < inResult->countResults; ++i) {
		printf("%d %s %s\n", inResult->headResult[i]->severityProblem, inResult->headResult[i]->hostName, inResult->headResult[i]->problemName);
	}
}

void resultListDelete(struct resultList * inResult) {
	if (inResult->countResults) {
		for(size_t i = 0; i < inResult->countResults; ++i) {
			if (inResult->headResult[i]) {
				if (inResult->headResult[i]->hostName) {
					free(inResult->headResult[i]->hostName);
					inResult->headResult[i]->hostName = NULL;
				}
				if (inResult->headResult[i]->problemName) {
					free(inResult->headResult[i]->problemName);
					inResult->headResult[i]->problemName = NULL;
				}
				free(inResult->headResult[i]);
				inResult->headResult[i] = NULL;
			}
		}
		if (inResult->headResult) free(inResult->headResult);
	}
}

void listDelete(struct zabbixList * inList) {
	if (inList->countStrings) {
		for(size_t i = 0; i < inList->countStrings; ++i) {
			if (inList->headList[i]) {
				free(inList->headList[i]);
				inList->headList[i] = NULL;
			}
		}
		if (inList->headList) free(inList->headList);
	}
}

char * listGetFirst(struct zabbixList * inList) {
	inList->getPos = 0;
	if (!inList->countStrings) return NULL;
	++inList->getPos;
	return inList->headList[0];
}

char * listGetNext(struct zabbixList * inList) {
	if (inList->getPos >= inList->countStrings) return NULL;
	++inList->getPos;
	return inList->headList[inList->getPos - 1];
}

void listInit(struct zabbixList * inList) {
	inList->headList = NULL;
	inList->countStrings = 0;
	inList->getPos = 0;
}

struct resultItem * lastResult(struct resultList * inResult) {
	if (inResult->countResults) {
		struct resultItem * cr = *inResult->headResult;
		while(cr->nextItem) {cr = cr->nextItem;};
		return cr;
	} else {
		return NULL;
	}
}

void resultAddItem(const size_t inSev, const char * inHost, const char * inProblem, struct resultList * inResult) {
	char * pHost = malloc(strlen(inHost)+1);
	if (pHost) {
		char * pProb = malloc(strlen(inProblem)+1);
		if (pProb) {
			struct resultItem * pItem = malloc(sizeof(struct resultItem));
			if (pItem) {
				pItem->severityProblem = inSev;
				*pHost = '\0';
				*pProb = '\0';
				strcpy(pHost, inHost);
				strcpy(pProb, inProblem);
				pItem->hostName = pHost;
				pItem->problemName = pProb;
				pItem->nextItem = NULL;
				if (inResult->countResults) {
					pItem->prevItem = lastResult(inResult);
					inResult->headResult = (struct resultItem **)realloc(inResult->headResult, (inResult->countResults + 1) * sizeof(struct resultItem *));
					inResult->headResult[inResult->countResults] = pItem;
				} else {
					pItem->prevItem = NULL;
					inResult->headResult = (struct resultItem **)malloc(sizeof(struct resultItem *));
					*inResult->headResult = pItem;
				}
				++inResult->countResults;
			} else {
				free(pProb);
				free(pHost);
			}
		} else {
			free(pHost);
		}
	}
}

void listAddString(const char * inStr, struct zabbixList * inList) {
	size_t len = strlen(inStr);
	char * ps = malloc(len + 1);
	if (ps) {
		*ps = '\0';
		strcpy(ps, inStr);
		if (inList->countStrings) {
			inList->headList = (char **)realloc(inList->headList, (inList->countStrings + 1) * sizeof(char*));
			inList->headList[inList->countStrings] = ps;
		} else {
			inList->headList = (char **)malloc(sizeof(char*));
			*inList->headList = ps;
		}
		++inList->countStrings;
		//printf("count in list: %d\n", inList->countStrings);
	}
}

void listTest(void) {
	struct zabbixList l1;
	listInit(&l1);
	listAddString("rydqw1671gf", &l1);
	listAddString("e2dq1gf", &l1);
	listAddString("ww3333rydqw1671gf", &l1);
	listAddString("jj444rydqw1671gf", &l1);
	char * item = NULL;
	if (item = listGetFirst(&l1)) {
		printf("%s\n", item);
		while(item = listGetNext(&l1)) {
			printf("%s\n", item);
		}
	}
	listDelete(&l1);
}

valgrind

Есть несколько замечаний.
Писать на процедурном С, не на С++ - это просто тренировка навыков или позиция? :wink:
Ну это я к тому, что для применения языка такого низкого уровня нужен повод. Причина простая - на столь низком уровне нет средств нормального управления памятью. Для такой мелочи - не особо страшно, но привычки так работать с памятью при программировании на С быть не должно. Языки высокого уровня, и даже просто С++ с нормальным STL имеет средства для строк и списков и вопрос мелкого фрагментирования памяти уже не касается программиста. А на С нужно про это думать самому. Для очень мелких объектов “бест практис” это просить память у системы большими кусками, если не хватило. И управлять самостоятельно мелочью.
Но я бы порекомендовал использовать нормальные реализации списков и строк в С++

“дурная голова рукам покоя не дает” (поговорка).

Есть OrangePI, работающий иногда в качестве шлюза между двумя сетями, есть ili9341, есть свободное время - почему бы не сделать колхоз, который повесить перед глазами на рабочем месте :slightly_smiling_face:

Проще запилить snmp trap, а не надевать штаны через голову.

Хм :thinking:
Не очень понял, что уж проще и правильнее к zabbix обращаться по его родному api?

https://www.opennet.ru/cgi-bin/opennet/man.cgi?topic=snmptrapd&category=8

Snmp ну вообще не мой случай, в zabbix же не только сетевые устройства, не буду же я для например Windows служб отдельный метод проверки делать, для Linux ещё какой то, проще с готового zabbix сервера взять уже готовую информацию о проблемах.

от ИИ советы нужны ?))
в массиве с плавающей памятью легко запутаться))) и лучше зарезервировать массив заранее, но у вас наверное дело в нехватке памяти…

Итак через сутки работы память занимаемая увеличилась почта в два раза.
Явно где то не освобождается, вопрос в моей программе косяк или в используемых библиотеках curl и/или json.

==4077==
==4077== HEAP SUMMARY:
==4077== in use at exit: 9,217 bytes in 147 blocks
==4077== total heap usage: 28,463 allocs, 28,316 frees, 3,122,308 bytes allocated
==4077==

Буду копать дальше, всем спасибо!

Для zabbix надо настроить snmptrap, чтобы он слал событие, а ваша программа в виде snmptrapd (стандартная для любой системы) будет реагировать. Не надо дергать zabbix за одно место ( api) , пусть он сам шлет. При этом вы получите стадартный обработчик, который без zabbix может работать.

Почуствуйте разницу. Система прибита гвоздями к zabbix или имеет стандартный протокол, который предназначен для этих целей. Тем более, что исходники snmptrapd есть и отлажены годами.

Понял, спасибо.

Буду пробовать.

Вам нужно написать враппер на malloc/free: на каждый malloc увеличивать счетчик, на каждый free() - уменьшать. Дайте программе поработать подольше, а сами следите за своим счетчиком. Ежели счетчик постоянно растет - у вас утечка. Если он ходит то вверх то вниз, то утечек нет.

Враппер может быть как целиком самописный (т.е. делает свои my_malloc(), my_calloc(), my_realloc() и my_free() и заменяете все вызовы на эти) . Тогда в учет не попадут аллокации сделанные glibc + надо везде заменять вызовы.

Если нужно учесть “всё, вообще всё” то вам нужно раскурить опции линкера

-Wl,--wrap=malloc -Wl,--wrap=free 

И почитать link-time function call wrapping: можно заставить линкер подменить вызов malloc на my_malloc, причем глобально: замена произойдет и в свежескомпиленном коде и в бинарниках (в glibc, например)

пиши враппер.

считай кол-во аллокаций.

для каждой аллокации сохраняй ее тип. Сохраняй thread_id/task_id того, кто аллокировал.

у меня это выглядит так:

buf = q_malloc(123, MTYPE_STRING);
buf = q_malloc(123, MTYPE_FILE_LIST);

иногда я вызываю q_memleaks(), которая мне выводит табличку:

  1. сколько аллокаций каждого типа
  2. список аллокаций (для каждого блока: размер, тип и task_id)

Вот и все. Так нашел несколько утечек.

PS: q_malloc() пишет статистику и вызывает malloc(). Все примитивно.

В релизе сделаешь #define q_malloc(_Size, _Type) malloc(_Size)