LINUX.ORG.RU

Утечка памяти в простой проге.

 ,


1

2

написал утилитку, удаляющую все файлы wine из каталогов «/home/safff/.local/share/mime/application» и «/safff/.local/share/mime/packages».
все работает, только наблюдается утечка памяти. поначалу вроде ничего, килобайт 500, но потом разрастается за 10 минут в 2 метра+.
не пойму откуда ента утечка

★★

Последнее исправление: safocl (всего исправлений: 1)
Ответ на: комментарий от aureliano15

Тут анонимус процитировал стандарт 2011, где это допускается. Но в c99 и более ранних стандартах main() всегда должна возвращать int.

Хорош п-деть. Специально скачал стандарт C99 1999-го года (не скорректированный от 2007-го), там тоже допускается.

В общем случае это так, но в твоём случае существуют константы из limits.h типа PATH_MAX и др., которые можно использовать, чтоб на этапе компиляции узнать максимальную длину пути.

Пути запросто могут быть длиннее PATH_MAX. Эта константа вообще нифига не гарантирует.

Сейчас что, весенние каникулы? Откуда все эти аурелианы лезут с их идиотскими советами?

anonymous
()
Ответ на: комментарий от anonymous

Специально скачал стандарт C99 1999-го года (не скорректированный от 2007-го), там тоже допускается.

Пожалуйста ссылку на стандарт и цитату.

Пути запросто могут быть длиннее PATH_MAX. Эта константа вообще нифига не гарантирует.

Эта константа гарантирует, что открыть файл или директорию, длина пути к которому/к которой больше, чем PATH_MAX, не удастся. А если открывать рекурсивно, то да, но ТС этого не делает. Кроме того, создавать пути, которые длиннее PATH_MAX, не рекомендуется, особенно программам, хоть и возможно, т. к. это нарушение стандарта.

весенние каникулы? Откуда все эти аурелианы лезут с их идиотскими советами?

Анонимусы, ты хотел сказать?

aureliano15 ★★
()
Ответ на: комментарий от aureliano15

Пожалуйста ссылку на стандарт и цитату.

Нет, ты притащишь пруфов, что void main() запрещёно.

anonymous
()
Ответ на: комментарий от anonymous

Пожалуйста ссылку на стандарт и цитату.

Нет

Понятно. Я так и думал.

aureliano15 ★★
()
Ответ на: комментарий от aureliano15

Пожалуйста ссылку на стандарт и цитату.

Т.е. у тебя нет стандарта C99 и ты его даже не открывал, но при этом пишешь

в c99 и более ранних стандартах main() всегда должна возвращать int.

anonymous
()
Ответ на: комментарий от anonymous

Т.е. у тебя нет стандарта C99 и ты его даже не открывал, но при этом пишешь

У меня есть стандарт (черновик, естественно) и я его открывал и прочитал там, что

5.1.2.2.1 Program startup

1 The function called at program startup is named main. The implementation declares no prototype for this function. It shall be defined with a return type of int and with no parameters:

int main(void) { /* ... */ }

or with two parameters (referred to here as argc and argv, though any names may be used, as they are local to the function in which they are declared):

int main(int argc, char *argv[]) { /* ... */ }

or equivalent;9) or in some other implementation-defined manner.

Т. е. чётко написано, что main() возвращает int, хотя допускаются альтернативные способы, определяемые реализацией (иначе говоря, нестандартные и непереносимые).

Кстати, сейчас посмотрел ещё раз стандарт c11, и там то же самое слово в слово. Так что да, согласен, что был неправ, когда говорил, что анонимус (не знаю, ты или другой) показал, что в c11 такое допускается. Ничего там не допускается, а в п. 5.1.2.2.3 говорится, что если реализовали такую нестандартную хрень, которая вместо int возвращает бог весть что, то и код возврата такой программы стандартом не определён. В общем, в следующий раз действительно надо поменьше читать анонимусов, более скептически относясь к их утверждениям, и побольше и повнимательнее стандарт. :-)

Поэтому и попросил «специально скаченный»

стандарт C99 1999-го года (не скорректированный от 2007-го),

в котором

тоже допускается.

И, разумеется, цитату (или хотя бы точный пункт), где это допускается. Или речь как раз о «some other implementation-defined manner»? Так это не стандарт, а что-то нестандартное, определяемое реализацией. Если это трактовать, как допущение, то и float main() допускается, и struct noname main(), и даже std::vector<std::string> main(), и плевать, что в си нет ни std, ни vector'ов, ни string'ов, ни вообще шаблонов. Вдруг в какой-то специфической реализации си всё это можно?

aureliano15 ★★
()
Ответ на: комментарий от aureliano15

согласен, что был неправ, когда говорил, что анонимус (не знаю, ты или другой) показал, что в c11 такое допускается. Ничего там не допускается

Как это не допускается, если «чётко написано, что main() возвращает int, хотя допускаются альтернативные способы»?

Или речь как раз о «some other implementation-defined manner»?

Да.

Так это не стандарт

Допускает — стандарт.

Если это трактовать, как допущение, то и float main() допускается, и struct noname main()

Можешь проверить в gcc. А потом в g++, начав хотя бы с void main().

Естественно, это допущение не разрешает нарушать другие пункты стандарта. Например, придётся определить struct noname, прежде чем использовать как тип возвращаемого значения.

std::vector<std::string> main(), и плевать, что в си нет ни std, ни vector'ов, ни string'ов, ни вообще шаблонов. Вдруг в какой-то специфической реализации си всё это можно?

Конечно плевать, ведь разговор был только про void, который есть в любой не специфической реализации.


Если хочешь увидеть, что такое «не допускается» — посмотри в стандарт C++ в соответствующем месте и сравни со стандартом C. Вот в C++ действительно void не допускается.

anonymous
()
Ответ на: комментарий от anonymous

Как это не допускается, если «чётко написано, что main() возвращает int, хотя допускаются альтернативные способы»?

Ну, то, что в принципе это возможно, понятно хотя бы потому, что у ТС'а всё откомпилялось. Я-то говорил о переносимом поведении, которое не полагается на конкретные реализации (в одной можно, в другой нельзя). И об определённом, а не рандомном, коде возврата из программы.

Если хочешь увидеть, что такое «не допускается» — посмотри в стандарт C++

Си++ вообще более строгий язык, чем си, и в этом, я считаю, одно из его преимуществ перед си, хотя есть и недостатки.

Впрочем, если включить опцию gcc -Werror, то си будет не менее строгим. :-)

aureliano15 ★★
()
Ответ на: комментарий от safocl

вот тут все норм

Вообще лучше текстом, а не картинкой. И смотреть проще, и особенно компилять и запускать.

В целом всё вроде окей.

Если более дотошно, то не окей:

  1. Как я уже говорил, не void main(), а int main(). И, соответственно, в операторах return указывать код возврата 0, если всё Ok, либо 1 в случае ошибки. Если коды ошибок могут быть разные, то выбрать константы из диапазона 64 — 78 для ошибок, предусмотренных в заголовке sysexits.h, и 79 — 113 для других возможных ошибок.
  2. char *path[2]; => const char *path[2];, т. к. указатели указывают на константные выражения. А ещё лучше сразу инициализировать
    const char *const path[2] =
        {
         "/home/safff/.local/share/mime/application", 
         "/home/safff/.local/share/mime/packages"
        };
    
    Тогда и содержимое строк случайно не изменишь, и сами указатели на эти строки.
  3. len1 и len2 — на самом деле константы, которые можно вычислить вне цикла. Каждый раз нужно вычислять только len3.
  4. malloc и free вызываются на каждой итерации цикла, что, имхо, не очень разумно. Разумнее, на мой взгляд, определить размер для 3-ей части буфера как NAME_MAX+1 и выделить буфер вне цикла, а потом так же удалить его вне цикла. Или даже сделать статическим. Как вариант, можно перераспределять память, только если она должна быть увеличена. Но в данной задаче это избыточно, т. к. есть константа NAME_MAX в limits.h (максимальная длина имени файла/каталога без завершающего нуля).

-Wmain -Werror

Лучше -Wall -Werror. А ещё лучше -Wall -Wextra -Wchkp -Werror. А в программах, которые должны быть совместимы с максимальным числом компиляторов в ущерб расширениям gcc, порой полезным, можно ещё -Wall -Wextra -Wchkp -Wpedantic -Werror. Хотя -Wpedantic не гарантирует 100% совместимости со стандартом.

aureliano15 ★★
()
Ответ на: комментарий от anonymous

ясна. ну ент уже кажется каким то памешательством.
я ваще в учебных целях енто издал, но с применением у себя.
по ентому, если ктота захатит чот использывать сам изменит.

safocl ★★
() автор топика
Ответ на: комментарий от aureliano15

char *path[2]; => const char *path[2];, т. к. указатели указывают на константные выражения

уже сделал, спс.

А ещё лучше сразу инициализировать

тоже уже думал, просто подумал чо будет нагляднее задавать так как я сделал.

len1 и len2 — на самом деле константы, которые можно вычислить вне цикла. Каждый раз нужно вычислять только len3.

справедливо, надо будет исправить.

malloc и free вызываются на каждой итерации цикла, что, имхо, не очень разумно. Разумнее, на мой взгляд, определить размер для 3-ей части буфера как NAME_MAX+1 и выделить буфер вне цикла, а потом так же удалить его вне цикла

но могут же разные длины названия файла попасться. и тем более, прога задумывалась как демон, по ентому вне постоянногго цыкла free нираз не сработает.

safocl ★★
() автор топика
Ответ на: комментарий от safocl

я ваще в учебных целях енто издал

Тем более надо сразу учиться писать хорошо. А хорошо — это в первую очередь ясно, переносимо и без ошибок, и только потом — разные хаки, нужность которых в общем случае спорна, хотя время от времени они нужны (без них был бы не нужен си).

тоже уже думал, просто подумал чо будет нагляднее задавать так как я сделал.

Это дело вкуса, но по мне простая инициализация массива нагляднее, чем много присвоений. Кроме того, в квадратных скобках можно не указывать размер:

const char *const path[] =
    {
     "/home/safff/.local/share/mime/application", 
     "/home/safff/.local/share/mime/packages"
    };

Тогда он будет определяться числом строк, и при добавлении/удалении строк не надо будет менять циферку в квадратных скобках. Мелочь, а приятно.

но могут же разные длины названия файла попасться.

Длина имени файла без завершающегося 0 не должна быть больше константы NAME_MAX. Попробуй создать файл с именем длиннее, а потом открыть его в open и прочитать readdir и той же scandir. Я не проверял, но успех если и возможен, то как минимум не гарантирован. Да и поле namelist[n]->d_name, думаю, не резиновое. Но на всякий случай можно проверять, длиннее это поле, чем NAME_MAX и, если что, игнорировать слишком длинные имена (которых скорее всего не будет). Ну и как самый-самый общий и надёжный (хотя, уверен, избыточный) вариант — это для файлов с именем такой же или меньшей длины, чем выделенный буфер, использовать буфер повторно, и только в случае недостатка длины перераспределять. Логика немного усложнится, но зато будет намного меньше лишних выделений и высвобождений памяти. Кстати, в стандартной библиотеке шаблонов си++ буфер для строк по умолчанию выделяется с запасом и при необходимости увеличивается сразу в 2 раза. Такой коэффициент далеко не все считают оптимальным. По мнению многих, достаточно увеличивать его на какую-то константу при перераспределении, а не сразу умножать на 2. Но практически все сходятся в том, что для данных произвольной переменной длины лучше сразу выделять память с запасом, чтобы потом реже перераспределять её.

aureliano15 ★★
()
Ответ на: комментарий от aureliano15

Тогда он будет определяться числом строк, и при добавлении/удалении строк не надо будет менять циферку в квадратных скобках. Мелочь, а приятно.

компилятор вроде автоматом создаст массив по количеству строк, точнее для него же енто лиш ссылки на области памяти где они хранятся, и он и выделит по количеству, а можно самому как понял указать, но думаю буду дальше больше на компилятора полагаться.

safocl ★★
() автор топика
Ответ на: комментарий от safocl

компилятор вроде автоматом создаст массив по количеству строк

Если ты не инициализируешь массив, то должен явно указать его размерность в квадратных скобках.

Если массив при объявлении сразу инициализируется, то размерность указывать необязательно. В этом случае размерность массива будет равна числу элементов, указанных через запятую в фигурных скобках или длине константной строки, включая завершающий 0, если речь идёт о массиве char, инициализируемым строкой (char s[] = "abc").

Если одновременно указать размерность в квадратных скобках при объявлении и явно инициализировать массив, то размерность будет определяться числом, недостающие элементы будут инициализированы 0 (для глобальных и статических массивов) или случайным значением (для локальных массивов), а лишние будут проигнорированы. Т. е. массив int a[2] = {1,2,3,4}; будет идентичен массиву int a[2] = {1,2};, а массив int a[4] = {1,2}; идентичен массиву int a[4] = {1,2,0,0};, если они глобальные или статические (static) либо массиву int a[4] = {1,2,случайное_число_из_стека,случайное_число_из_стека}; в противном случае.

aureliano15 ★★
()
Последнее исправление: aureliano15 (всего исправлений: 2)
Ответ на: комментарий от safocl

вышло так

Если вкратце, то я бы

  1. Вынес вычисление path_i из цикла, т. к. его не нужно вычислять на каждой итерации. Но тогда счётчик должен быть другим, а path_i — ограничителем счётчика.
  2. Вынес проверку результата malloc из цикла в сразу после malloc.
  3. Вставил бы regfree(), про который ты снова забыл. Сейчас это не так критично, т. к. память выделяется 1 раз до конца программы, но освобождать память считается хорошим стилем, даже если её всё равно освободит система.
  4. Ну и разделитель не нужен, т. к. это часть пути.
  5. Возвращать из main -1 неправильно. Хоть с точки зрения main это и int, который может меняться от -2 млрд до +2 млрд, но с точки зрения оболочки это unsigned char, который меняется от 0 до 255.

Если подробно, то см. мой патч к твоей программе. Наложить его можно утилитой patch. И да, переименуй свою программу из find во что-то другое, чтоб не путать её со стандартной утилитой find.

aureliano15 ★★
()
Ответ на: комментарий от aureliano15

Вынес вычисление path_i из цикла, т. к. его не нужно вычислять на каждой итерации. Но тогда счётчик должен быть другим, а path_i — ограничителем счётчика.

ну а как я тада буду два раза пути перебирать? так и получается чо два раза надо юзать по каждому пути.

Вынес проверку результата malloc из цикла в сразу после malloc.

понял, исправлю.

чот на джини не долга хранится паст. мой уже не доступен.

единственна не пойму, почему ты убрал константу на указатель result(чо бы указатель был константным)?

safocl ★★
() автор топика
Последнее исправление: safocl (всего исправлений: 1)
Ответ на: комментарий от aureliano15

и не пойму, чем тебе конструкция

while (path_i--)
не нравится?

safocl ★★
() автор топика
Ответ на: комментарий от safocl

по таким выводам принтф выводит следующее:

path_i
присвоение namelist
присвоение n
n
n
n
n
n
n
n
n
path_i
присвоение namelist
присвоение n
n
n
n
n
n
n
n
sleep
path_i
присвоение namelist
/tmp/geany_run_script_XQ7HGZ.sh: строка 7: 11626 Ошибка сегментирования                   (стек памяти сброшен на диск) "./main"


------------------
(program exited with code: 139)
Press return to continue

как понимаю не проходит второй поиск, но почему не могу понять.

safocl ★★
() автор топика
Последнее исправление: safocl (всего исправлений: 1)
Ответ на: комментарий от safocl

аа стоп.. понял.. получается чо переменная path_i ушла в минус.. я же ее терь тоже вынес за цыкл.

апд - все таки поставил цыкл for из твоего примера.

safocl ★★
() автор топика
Последнее исправление: safocl (всего исправлений: 1)
Ответ на: комментарий от safocl

Вынес вычисление path_i из цикла, т. к. его не нужно вычислять на каждой итерации. Но тогда счётчик должен быть другим, а path_i — ограничителем счётчика.

ну а как я тада буду два раза пути перебирать? так и получается чо два раза надо юзать по каждому пути.

Я говорю про вычисление size_t path_n = sizeof(path)/sizeof(path[0]);. Хотя, в принципе, эта дробь всё равно вычисляется на этапе компиляции, поэтому можно оставить и так, как было у тебя. Разницы не будет.

чот на джини не долга хранится паст. мой уже не доступен.

Там, по-моему, можно срок жизни задавать.

единственна не пойму, почему ты убрал константу на указатель result(чо бы указатель был константным)?

Да, я был не прав. Не обратил внимания, что присвоение происходит при инициализации. Если бы присвоение происходило в другом месте, то такой код не работал бы. А так лучше оставить const.

хз, но я все равно не пойму чем лучше strcpy() чем memcpy()?

Особой разницы нет. Просто проще вызвать strcpy(), которая сама вычислит длину строки, чем делать то же самое самому: сначала вызывать strlen(), сохраняя результат в переменной, а затем memcpy(). А так — дело вкуса. Если нравится лишние буквы писать, ради бога. :-)

и не пойму, чем тебе конструкция while (path_i--) не нравится?

Да ничем не не нравится. Я же говорю: вынес вычисление path_i из цикла, назвав её path_n, поэтому сделал цикл for по i от 0 до path_n.

терь почему то сегфолтится.

Естественно. Потому что тебе надо было либо сделать как у меня, т. е. не менять в цикле path_n, либо оставить, как было раньше. А ты вынес вычисление path_i из цикла, как у меня, но в цикле продолжаешь её менять (а этого у меня нет, если ты заметил). В результате, после первой итерации самого внешнего цикла, она уменьшается до -1 (т. к. декремент постфиксный, то while считывает 0 ещё до декремента и завершается), а потом не восстанавливается, и ты получаешь индекс path[255], которого у тебя нет (255, а не -1, потому, что path_i у тебя unsigned char, поэтому при уменьшении 0 на 1 он переходит в 255, если бы тип был знаковым, то получалось бы -1, что тоже было бы неправильно). В общем, или сделай, как у меня, или верни, как было, а ужа с ежом не скрещивай. :-)

по таким выводам принтф выводит следующее:

И ещё очень советую освоить дебагер. Лучше gdb (там надо запомнить несколько команд, но он легче, гибче и универсальнее) либо какую-нибудь графическую надстройку над gdb. printf'ами тоже можно выводить чек-поинты (и порой так и делают), но чаще использовать дебагер удобнее. Потратив день-другой на освоение дебагера, потом ты сэкономишь массу времени и сил даже на этапе обучения. А уж при написании и отладке реальных программ дебагер просто незаменим.

апд - все таки поставил цыкл for из твоего примера.

Правильно сделал. Циклы for интуитивно понятнее, чем while, т. к. там видно откуда и докуда меняется счётчик, а в while — только докуда.

aureliano15 ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.