Какие функции в стандартной библиотеке C обычно поощряют плохую практику?

Это вдохновлено этим вопросом и комментариями к одному конкретному ответу, в котором я узнал, что strncpy не очень безопасная функция обработки строк в C, и она дополняет нулями, пока не достигнет n, о чем я не знал.

В частности, чтобы процитировать R..

strncpy не завершается нулем и заполняет нулем всю оставшуюся часть целевого буфера, что является огромной тратой времени. Вы можете обойти первое, добавив собственное пустое заполнение, но не последнее. Он никогда не предназначался для использования в качестве функции «безопасной обработки строк», но для работы с полями фиксированного размера в таблицах каталогов Unix и файлах базы данных. snprintf(dest, n, "%s", src) - единственный правильный "безопасный strcpy" в стандартном C, но он, вероятно, будет намного медленнее. Между прочим, усечение само по себе может быть серьезной ошибкой и в некоторых случаях может привести к повышению привилегий или отказу в обслуживании, поэтому использование «безопасных» строковых функций, которые усекают свой вывод в проблеме, не является способом сделать ее «безопасной» или « безопасный". Вместо этого вы должны убедиться, что буфер назначения имеет правильный размер, и просто использовать strcpy (или, что еще лучше, memcpy, если вы уже знаете длину исходной строки).

И от Джонатана Леффлера

Обратите внимание, что интерфейс strncat() еще более сбивает с толку, чем strncpy() — опять же, что это за аргумент длины? Это не то, что вы ожидаете, основываясь на том, что вы предоставляете strncpy() и т. д., поэтому он более подвержен ошибкам, даже чем strncpy(). Что касается копирования строк, я все больше склоняюсь к мнению, что есть веский аргумент в пользу того, что вам нужна только memmove(), потому что вы всегда заранее знаете все размеры и заранее убедитесь, что места достаточно. Используйте memmove() вместо любого из методов strcpy(), strcat(), strncpy(), strncat(), memcpy().

Итак, я явно немного заржавел в стандартной библиотеке C. Поэтому я хотел бы задать вопрос:

Какие функции стандартной библиотеки C используются ненадлежащим образом/таким образом, что это может вызвать/привести к проблемам безопасности/дефектам кода/неэффективности?

В интересах объективности у меня есть ряд критериев для ответа:

  • Пожалуйста, если можете, укажите конструктивные причины рассматриваемой функции, т.е. ее предполагаемое назначение.
  • Пожалуйста, выделите неправомерное использование кода в настоящее время.
  • Укажите, почему такое неправильное использование может привести к возникновению проблемы. Я знаю, что это должно быть очевидно, но это предотвращает мягкие ответы.

Пожалуйста, избегайте:

  • Дебаты по поводу соглашений об именах функций (за исключением случаев, когда это однозначно вызывает путаницу).
  • «Я предпочитаю x, а не y» — предпочтения в порядке, они есть у всех, но меня интересуют настоящие неожиданные побочные эффекты и способы защиты от них.

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

Я также работаю в соответствии с C99.


person Community    schedule 03.01.2011    source источник
comment
Любую функцию можно использовать ненадлежащим образом и таким образом, что это может привести к дырам в безопасности.   -  person Falmarri    schedule 04.01.2011
comment
@Falmarri - но некоторые часто используются ненадлежащим образом там, где другие нет, некоторые, похоже, поощряют неправильное использование там, где другие этого не делают.   -  person Steve314    schedule 04.01.2011


Ответы (14)


Распространенная ошибка при работе с функцией strtok() состоит в том, что предполагается, что анализируемая строка остается неизменной, тогда как на самом деле она заменяет символ-разделитель на '\0'.

Кроме того, strtok() используется для последующих вызовов до тех пор, пока вся строка не будет размечена. Некоторые реализации библиотек хранят внутренний статус strtok() в глобальной переменной, что может вызвать некоторые неприятные сюрпризы, если strtok() вызывается из нескольких потоков одновременно.

Стандарт безопасного кодирования CERT C многие из этих ловушек, о которых вы спрашивали.

person Community    schedule 03.01.2011
comment
+1 За отражение моих мыслей о strtok() и за упоминание стандарта безопасного кодирования CERT C. - person Jonathan Leffler; 04.01.2011
comment
+1, это отличная ссылка, также @Jonathan извините, что цитирую вас, но вы, ребята, заставили меня подумать, что мне определенно нужно понимать, что происходит, гораздо яснее. Надеюсь, ты не против быть знаменитым! - person ; 04.01.2011
comment
Технически это библиотечная функция, а не компилятор, которая хранит состояние. Большая проблема заключается в том, что если вы изолируете токен в своей строке, а затем вызываете функцию, которая, без вашего ведома, сама вызывает strtok(). - person Jonathan Leffler; 04.01.2011
comment
@Ninefingers: Я переживу свои 15 секунд позора :D - person Jonathan Leffler; 04.01.2011
comment
strtok требуется для глобального сохранения своего внутреннего состояния даже с потоками, по крайней мере, в среде POSIX, где потоки указаны. Это связано с тем, что соответствующая программа может начать синтаксический анализ в одном потоке и закончить в другом. Конечно, у MS есть собственная версия потоков, в которой они могут указывать другое (локальное для потока) поведение, как они это делают, но это конфликтует с POSIX. - person R.. GitHub STOP HELPING ICE; 04.01.2011
comment
Теперь это вики сообщества, что хорошо, но похоже, что я должен принять ответ, поэтому я принимаю его для стандарта безопасного кодирования CERT C, который предоставляет массу полезной информации. - person ; 05.01.2011
comment
Я озадачен тем, почему никто не упомянул strtok_r как (немного) менее запутанный, поскольку он не сохраняет глобальное состояние. - person abligh; 12.01.2015

Какие функции стандартной библиотеки C используются ненадлежащим образом/способами, которые могут вызвать/привести к проблемам безопасности/дефектам кода/неэффективности?

Я собираюсь пойти с очевидным:

char *gets(char *s);

С его замечательной особенностью, что использовать его по назначению просто невозможно.

person Community    schedule 03.01.2011
comment
MacOS X фактически выводит предупреждение во время выполнения, когда вы его используете. - person onemasse; 04.01.2011
comment
Теперь я могу честно сказать, что это единственная функция, которой я никогда не злоупотреблял, потому что на самом деле я никогда ею не пользовался. - person ; 04.01.2011
comment
gets(): абсолютный ноль безопасности программного обеспечения. - person j_random_hacker; 04.01.2011
comment
Обратите внимание, что C0x удалит gets() из стандарта. К сожалению, пройдет еще 10-20 лет после того, как это будет завершено, прежде чем оно будет удалено из большинства реализаций - это диктует обратная совместимость с небезопасностью. - person Jonathan Leffler; 04.01.2011
comment
@onemasse: правда? Я не заметил (но ведь я его не использую даже в одноразовом коде!). Гораздо лучше, что он предупреждает об этом, чем о mktemp(), который я периодически вижу в коде, над которым работаю. - person Jonathan Leffler; 04.01.2011
comment
@Jonathan и MSVC не будут его поддерживать ... особенно если он не поддерживает C99. - person ; 04.01.2011
comment
MSVC может. Их сделка с комитетом заключается в том, что они поддержат новый стандарт, если комитет добавит все свои отвратительные *_s безопасные функции в стандарт, чтобы заставить реализации *nix загрязнять себя им. ;-) - person R.. GitHub STOP HELPING ICE; 04.01.2011
comment
@Jonathan: я не думаю, что это займет 20 лет. Я ожидаю, что большинство реализаций *nix, по крайней мере, будут довольно быстро защищать его в заголовке с помощью #if defined IM_A_MORON_LET_ME_SHOOT_MYSELF_IN_THE_FOOT. - person Stephen Canon; 04.01.2011
comment
@JonathanLeffler: стандарт ISO C 2011 года действительно удалил gets() из стандартной библиотеки. - person Keith Thompson; 13.02.2014
comment
@KeithThompson: Да! Теперь нужно удалить gets() из системных библиотек повсюду или заменить в системной библиотеке char *gets(char *str) { abort(); } вторичной библиотекой -lgets, которую нужно добавить в строку ссылки, чтобы получить небезопасную функцию gets() в старом стиле. Предупреждение компоновщика было бы хорошо (например, для mktemp()) - если это еще не произошло. Безусловное предупреждение компилятора тоже было бы неплохо. - person Jonathan Leffler; 13.02.2014

Почти во всех случаях не следует использовать atoi() (это также относится к atof(), atol() и atoll()).

Это связано с тем, что эти функции вообще не обнаруживают ошибки вне допустимого диапазона — стандарт просто говорит "Если значение результата не может быть представлено, поведение не определено".. Таким образом, единственный случай, когда их можно безопасно использовать, — это если вы можете доказать, что ввод определенно будет в пределах диапазона (например, если вы передаете строку длиной 4 или меньше в atoi(), она не может быть вне диапазона).

Вместо этого используйте одну из функций семейства strtol().

person Community    schedule 03.01.2011
comment
+1 за указание на (в основном теоретическую, но все же) опасность atoi и UB. - person R.. GitHub STOP HELPING ICE; 04.01.2011
comment
Отличный момент. Нет причин использовать ato*. - person Stephen Canon; 04.01.2011
comment
На самом деле это очень удобно, если вы знаете, на какой платформе будет работать ваш код, что, скорее всего, вы знаете. Например. MSVC говорит, что возвращаемое значение равно 0 для atoi и _wtoi, если ввод не может быть преобразован в значение этого типа., так что это довольно хорошо защищено. (Кроме того, это еще один пример, когда неопределенный и определенный реализацией на самом деле не совсем разные — они оба могут быть определены реализацией.) - person user541686; 12.11.2011

Давайте распространим вопрос на интерфейсы в более широком смысле.

errno:

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

  • потому что это может вызвать накладные расходы для вызывающего абонента при доступе к значению, чтобы проверить «ошибку» (что может быть просто исключительным событием)
  • потому что в некоторых местах даже навязывается, что вызывающая сторона очищает эту «переменную» перед вызовом библиотеки
  • потому что он реализует простой возврат ошибки, устанавливая глобальное состояние библиотеки.

Предстоящий стандарт дает определение errno немного более прямолинейно, но эти уродства остаются.

person Community    schedule 03.01.2011
comment
Хотя это немного уродливо, в errno очень мало подвержено ошибкам или опасно. Это макрос, который возвращает модифицируемое значение lvalue типа int, которое достаточно четко определено. Насколько я могу судить, это означает, что вы можете взять и сохранить его адрес и получить доступ к текущему значению через этот адрес, если хотите. Единственная плохая практика, которую я могу придумать, которую может поощрять errno, это (1) моделирование отчетов об ошибках ваших собственных библиотек и (2) использование &errno в качестве дешевого универсально переносимого идентификатора потока. :-) - person R.. GitHub STOP HELPING ICE; 04.01.2011
comment
Цитата из стандарта: Макрос... errno, который расширяется до изменяемого lvalue типа int.... Итак, ясно, что это макрос. - person Raedwald; 05.01.2011
comment
@Raedwald: да, но неясно, как получается lvalue. Я думаю, что в настоящее время это обычно вызов функции. - person Jens Gustedt; 05.01.2011
comment
Конечно, errno — это макрос, а не extern int, именно для того, чтобы придать реализации такую ​​гибкость? Почему это проблема, что это такое? - person Raedwald; 05.01.2011
comment
Действительно, я не вижу никаких проблем с тем, что это модифицируемое значение lvalue, определение которого зависит от реализации. - person R.. GitHub STOP HELPING ICE; 05.01.2011
comment
@R..: Это очень плохой переносимый идентификатор потока, будет ли он работать для компиляторов/платформ без поддержки TLS? - person Matt Joiner; 06.01.2011
comment
@Jens Gustedt: Не могли бы вы рассказать о предполагаемых изменениях в определении errno в готовящемся стандарте? Мне любопытно. - person Matt Joiner; 06.01.2011
comment
@Matt: в нем прямо указано, что у него есть продолжительность локального хранения потока. Там это возможно, так как новый стандарт будет иметь модель потоков, довольно близкую к POSIX BTW. - person Jens Gustedt; 06.01.2011
comment
@Raedwald: о проблеме, возможно, сказано слишком много, но накладные расходы, которые создает простой errno = 0;, очень трудно оценить для приложения. - person Jens Gustedt; 06.01.2011
comment
@Matt: Если это изменяемое значение lvalue типа int, его адрес допустим, и он не может совпадать с адресом errno другого потока. Это не зависит от TLS на уровне компилятора. Например, &(*__errno_location()) совпадает с __errno_location(). Если вы пишете свой собственный код блокировки, используя атомарные примитивы (C1x, встроенные модули gcc или asm), &errno кажется самым безопасным идентификатором владельца, который вы можете получить, не привязываясь к конкретной реализации потоков (pthreads, solaris, windows и т. ). Хотя согласен, что немного зло... - person R.. GitHub STOP HELPING ICE; 06.01.2011
comment
@R, @Matt: я думаю, что стандарт не требует, чтобы значение lvalue было одинаковым между двумя последующими использованиями макроса одним и тем же потоком. Хотя я должен признать, что это звучит немного безумно, предполагать иначе, но вы можете себе представить, что библиотека помимо идентификатора потока отслеживает какое-то другое состояние потока и тут же переназначает новый адрес. - person Jens Gustedt; 06.01.2011

Часто встречается файл strtok_r.

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

person Community    schedule 03.01.2011
comment
Я собирался сказать, что это должен быть комментарий, а не ответ, но вы не можете комментировать без представителя, так что вот, приготовьте. - person Stephen Canon; 04.01.2011
comment
В тот момент, когда вы говорите, что часто есть strtok_r(), вы иногда сталкиваетесь с тем, что его нет, и что вы собираетесь делать, когда он недоступен? Второстепенным вопросом является предполагаемая платформа - вопрос касается C99, где strtok_r() нет (как и strtok_s() вообще - из TR 24731-1). - person Jonathan Leffler; 04.01.2011

Я бы поставил printf и scanf довольно высоко в этом списке. Тот факт, что вы должны точно указать спецификаторы форматирования, делает эти функции сложными в использовании и очень легко ошибиться. Также очень сложно избежать переполнения буфера при считывании данных. Более того, «уязвимость строки формата printf», вероятно, создала бесчисленные дыры в безопасности, когда благонамеренные программисты указывают строки, указанные клиентом, в качестве первого аргумента для printf, только для того, чтобы обнаружить, что стек разбит, а безопасность скомпрометирована много лет спустя.

person Community    schedule 03.01.2011
comment
если ваш компилятор не может сказать вам, что вы использовали %x с числом int, откажитесь от него или включите его предупреждающие флаги. - person BatchyX; 04.01.2011
comment
Я не согласен. Только когда строка формата вычисляется во время выполнения вместо постоянной строки, они становятся опасными. В GCC даже есть хорошая опция предупреждения -Wformat-nonliteral для этого случая (которую, конечно, следует комбинировать с -Werror, чтобы выдавать предупреждения об ошибках). - person Adam Rosenfield; 04.01.2011
comment
пока вы это делаете, просто включите -Wall, что также включает -Wformat. - person BatchyX; 04.01.2011
comment
@Adam: Это небезопасно только в том случае, если вы используете строки формата, вычисляемые во время выполнения (или спецификатор формата %n), но их все равно легко ошибиться (хотя предупреждения компилятора действительно помогают). - person j_random_hacker; 04.01.2011
comment
использование спецификатора формата %n совершенно нормально при правильном использовании. Это даже необходимо в некоторых случаях (вот почему это там). - person BatchyX; 04.01.2011
comment
Функция sprintf() также может привести к переполнению буфера, если переменные выводятся как нечто большее, чем ожидалось. Например, код может ожидать, что unsigned long займет не более одиннадцати байтов (десять цифр и нулевой терминатор), но произойдет сбой в 64-битных системах, где unsigned long может занять 21 байт. - person supercat; 04.01.2011
comment
Семейство printf (особенно snprintf) на самом деле является лучшим и наиболее безопасным способом создания строк в стандартной библиотеке C, если вы хоть немного разбираетесь в C. Я не думаю, что эти функции вообще виноваты в том, что идиоты передают неформатные строки вместо аргумента строки формата... - person R.. GitHub STOP HELPING ICE; 04.01.2011
comment
@R.: Я не думаю, что в 1990-х нужно было быть идиотом, например. sprintf %lu в 16-байтовый буфер, не проверяя числовое значение рассматриваемых данных. ИМХО, что очень плохо, так это то, что нет стандартного vgprintf, который принимал бы (в дополнение к аргументам vprintf) void* и указатель на функцию, которая принимает void * и char; такая функция может быть использована для синтеза любого из вариантов printf или vprintf, а также sprintf с ограниченными рамками, консольной printf с переносом строки или любой другой необходимой функции printf-ish. - person supercat; 04.01.2011
comment
@supercat: я бы сказал, что всегда было глупо использовать константу, независимую от соответствующего выражения sizeof, в качестве размера буфера. Что касается вашего теоретического vgprintf, то было бы неплохо, но проблема в том, на какой уровень его поставить. Я уверен, что многие люди предпочли бы иметь объекты FILE с обратными вызовами, предоставляемыми пользователем, и использовать с ними vfprintf. Конечно, это было бы сложнее использовать в простейших случаях и могло бы наложить нежелательные ограничения на возможные реализации stdio. - person R.. GitHub STOP HELPING ICE; 05.01.2011
comment
Еще одна вещь, о которой следует подумать в отношении vgprintf... потребуются ли обратные вызовы для приема данных в любом блоке, в который их отправляет реализация, или они будут ожидать целые поля за раз? В последнем случае это требует динамического выделения в реализации и, таким образом, имеет условия сбоя из-за нехватки памяти. В первом случае vgprintf требует O(1) пространства (хотя, возможно, до 8 КБ или около того, если требуется точный вывод с плавающей запятой). - person R.. GitHub STOP HELPING ICE; 05.01.2011
comment
@R.: Как можно использовать sizeof() для вычисления размера строки, необходимой для размещения десятичного печатного числа? Просто цифра (CHAR_BITS * sizeof(unsigned long))››6+2 или что-то в этом роде? Что касается vgprintf, то он примет значение void*, которое будет передано функции вывода. Для fprintf это будет ФАЙЛ*; для sprintf это будет char**; для snprintf это может быть указатель на локальную структуру с char*, длиной на данный момент и максимальной длиной. Нет необходимости в динамическом размещении. Тот, кто вызывает vgprintf, будет нести ответственность за обеспечение того, чтобы переданный указатель подходил для переданной функции. - person supercat; 05.01.2011
comment
@R.: Кстати, я думаю, что vgprintf - хороший способ объяснить ценность делегатов в объектно-ориентированных языках. В C необходимо передавать отдельно указатель на функцию и некоторые данные, а также вручную обеспечивать, чтобы функции были связаны только с теми типами данных, которые они ожидают. Делегаты позволяют связывать указатель на функцию с частью данных, которые будут проверяться во время компиляции, чтобы убедиться, что это правильный тип для функции. - person supercat; 05.01.2011
comment
@supercat: я всегда использую 3*sizeof(type)+2, потому что я не пишу код обработки текста, за исключением POSIX и POSIX-подобных систем, где CHAR_BIT должно быть 8. Но вы можете ввести CHAR_BIT, если хотите. - person R.. GitHub STOP HELPING ICE; 06.01.2011

Любая из функций, управляющих глобальным состоянием, например gmtime() или localtime(). Эти функции просто нельзя безопасно использовать в нескольких потоках.

EDIT: rand() похоже, находится в той же категории. По крайней мере, нет никаких гарантий потокобезопасности, а в моей системе Linux справочная страница предупреждает, что она не поддерживает повторный вход и не является потокобезопасной.

person Community    schedule 03.01.2011
comment
Насколько я знаю, единственный совместимый способ сделать rand потокобезопасным — это синхронизировать его с мьютексом, что немного повредит производительности. Предполагается, что для данного начального числа всегда будет возвращаться одна и та же последовательность псевдослучайных чисел, поэтому использование локального состояния потока может нарушить эту семантику в совместимых приложениях, которые используют собственный мьютекс вокруг вызовов rand. - person R.. GitHub STOP HELPING ICE; 05.01.2011
comment
... или которые изначально используют srand и rand только в основном потоке, а затем после инициализации продолжают использовать их во вновь созданном потоке, никогда больше не используя их в основном потоке. - person R.. GitHub STOP HELPING ICE; 06.01.2011

Один из моих самых неприятных моментов — strtok(), потому что он не допускает повторного входа и взламывает строку, которую он обрабатывает, на части, вставляя NUL в конце каждого маркера, который он изолирует. Проблем с этим легион; к сожалению, его часто преподносят как решение проблемы, но так же часто это и есть сама проблема. Не всегда - его можно безопасно использовать. Но только если вы будете осторожны. То же самое относится и к большинству функций, за исключением gets(), которую нельзя использовать безопасно.

person Community    schedule 03.01.2011
comment
Стоит отметить, что strtok(), вероятно, был добавлен, потому что шаблон (strchr() или strpbrk() для поиска разделителя; перезаписать разделитель на '\0'; цикл до тех пор, пока разделители не исчезнут) очень распространен. - person caf; 04.01.2011
comment
@caf: это работает, если вам не нужно знать, что такое разделитель, но не тогда, когда вам нужно знать разделитель. Смотрите вопрос, связанный с моим ответом, и жалкие оправдания извинений от тех, кто защищает strtok(). Я не часто использую отрицательные голоса; там есть два ответа с минусами от меня! - person Jonathan Leffler; 04.01.2011
comment
Ну, я склонен думать, что strtok() немного несправедливо оклеветан, даже если некоторые из критических замечаний справедливы. Возможно, потому, что я встречал более одного случая, когда это было именно тем, что я хотел — до тех пор, пока вы остаетесь в пределах своей предполагаемой области (анализируя простые строки, такие как PATH переменные), а не пытаетесь анализировать сложные документы с помощью это, я не думаю, что это так уж плохо. - person caf; 04.01.2011
comment
@caf: Проблема в том, что, как только кто-то хочет взять ваш код и использовать его в настройках библиотеки, а не в main(), он сталкивается с неприятным сюрпризом и должен вырвать strtok и заменить его разумной альтернативой. - person R.. GitHub STOP HELPING ICE; 04.01.2011
comment
@R.: Ну, да - все вышеперечисленное следует принимать по модулю обычных предостережений, которые применяются ко всем нереентерабельным функциям. - person caf; 04.01.2011

Насчет realloc уже есть один ответ, но у меня другое мнение. Много раз я видел, как люди пишут realloc, когда имеют в виду free; malloc - другими словами, когда у них есть буфер, полный мусора, который должен изменить размер перед сохранением новых данных. Это, конечно, приводит к потенциально большому memcpy кешированию хлама, который вот-вот будет перезаписан.

При правильном использовании с растущими данными (таким образом, чтобы избежать наихудшей O(n^2) производительности для увеличения объекта до размера n, т.е. увеличения буфера геометрически, а не линейно, когда вам не хватает места), realloc имеет сомнительное преимущество перед простым выполнением ваших собственных действий. новый цикл malloc, memcpy и free. Единственный способ, с помощью которого realloc может когда-либо избежать этого внутри, - это когда вы работаете с одним объектом в верхней части кучи.

Если вам нравится заполнять нулями новые объекты с помощью calloc, легко забыть, что realloc не заполнит новую часть нулями.

И, наконец, еще одно распространенное использование realloc — выделить больше, чем вам нужно, а затем уменьшить размер выделенного объекта до необходимого размера. Но на самом деле это может быть вредным (дополнительное выделение и memcpy) для реализаций, которые строго разделяют фрагменты по размеру, а в других случаях может увеличить фрагментацию (путем отделения части большого свободного фрагмента для хранения нового небольшого объекта вместо использования существующего). небольшой свободный кусок).

Не уверен, что я бы сказал, что realloc поощряет плохую практику, но я бы остерегся этой функции.

person Community    schedule 04.01.2011
comment
Я предполагаю, что объект, подвергаемый realloc()-редактированию, может расширяться на месте достаточно часто, чтобы сделать его более предпочтительным, чем free(); malloc();. И ваша точка зрения о realloc() уменьшении до меньшего размера, вызывающего фрагментацию, слаба, я думаю - да, это могло вызвать фрагментацию, а именно точную степень фрагментации, которая была бы вызвана, если бы мы знали правильный размер, который нужно запрашивать во время исходный malloc() вызов. - person j_random_hacker; 04.01.2011
comment
Нет. В худшем случае перераспределение и перераспределение вниз обеспечивают такую ​​же сильную фрагментацию, как если бы вы никогда не выполняли перераспределение. Это никогда не будет так хорошо, как выделение правильной суммы для начала, если только правильную сумму нельзя было получить только путем отделения от большего свободного куска. Что касается расширения на месте, если вы говорите об объекте, который увеличивается с течением времени (например, чтение буфера в длинном файле), вы можете увеличивать его только геометрически, иначе вы рискуете O(n^2) временем копирования. При геометрическом росте расширение на месте почти никогда невозможно. - person R.. GitHub STOP HELPING ICE; 04.01.2011
comment
В качестве примера фрагментации предположим, что у вас есть программа, которая выделяет 100 000 фрагментов и перераспределяет их до ~ 1 000 и не выполняет никаких других операций по выделению. После heap_size/100k (= 20000 во многих 32-разрядных системах) выделений следующее произойдет с ошибкой, несмотря на то, что используется только 1% кучи. Распределитель может избежать этой проблемы, всегда перемещая фрагменты, когда их размер сильно уменьшается, за счет некоторой производительности... - person R.. GitHub STOP HELPING ICE; 04.01.2011
comment
Посмотрите, что вы имеете в виду под фрагментацией. Но ваш сценарий маловероятен: обычно перераспределение происходит вскоре после первоначального выделения, до того, как произойдут другие выделения. Кроме того, я не вижу другого, лучшего способа подойти к проблеме, когда вы не знаете необходимый размер - единственная возможная разумная альтернатива состоит в том, чтобы попробовать экспоненциально большие предположения, пока не подойдет одно, но (а) обычно это больше проблем, чем это стоит, (b) он требует O(log n) выделений и (c) он полагается на возможность повторного получения данных, которые вы пытаетесь сохранить несколько раз (невозможно, если, скажем, вы читаете из канала). - person j_random_hacker; 04.01.2011
comment
Также не уверен, почему вы думаете, что рост в геометрической прогрессии и на месте почти никогда не возможен. У меня нет статистики (и я подозреваю, что у вас нет), но я ожидаю, что приличная доля перераспределений действует на самый последний (пере)распределенный блок, который, вероятно, будет таким расширяемым. Я думаю, что самое сильное, что вы могли бы сказать против realloc() здесь, это то, что эта вероятность расширения на месте самого последнего выделенного блока снижает вероятность фактического получения поведения O (n ^ 2) от плохо продуманного (линейно-растущего). ) схемы роста, тем самым поощряя эту порочную практику. - person j_random_hacker; 04.01.2011
comment
Реализация malloc, направленная на предотвращение фрагментации, будет стремиться удовлетворить все выделения, используя свободный фрагмент, максимально приближенный к запрошенному размеру. В dlmalloc-подобных реализациях с логарифмической шкалой свободного бина фрагмент, используемый для удовлетворения распределения, никогда не будет более чем в небольшой раз (я полагаю, в 1,5 раза) больше, чем запрос, если только нет доступных свободных фрагментов. Конечно, это возможно, но я думаю, что маловероятно, что большие свободные фрагменты не будут находиться в верхней части кучи, но не будет маленьких свободных фрагментов. - person R.. GitHub STOP HELPING ICE; 05.01.2011
comment
Что касается того, что делать, когда вы не знаете необходимый размер, мой любимый подход почти всегда состоит в том, чтобы вычислить его, даже если это означает, что вы выполняете вычисления один раз и отбрасываете результаты, а затем запускаете их снова. (Например, сначала вызовите snprintf с нулевым размером.) Если вы действительно хотите использовать подход с перераспределением и сокращением, вы можете просто вызвать malloc, memcpy и free самостоятельно и вернуться к попытке realloc, если malloc не удается. Это безопасно от вызывания фрагментации. - person R.. GitHub STOP HELPING ICE; 05.01.2011
comment
Хороший разговор :) маловероятно, что большие свободные фрагменты не будут находиться в верхней части кучи, но нет маленьких свободных фрагментов - я согласен. Я бы сказал, что у вас гораздо больше шансов иметь стекоподобную последовательность выделений и освобождений, которая вообще не оставляет (или очень мало) пробелов, так что существует высокая вероятность того, что любой заданный malloc() вызов будет выделен из конец выделенной в данный момент памяти и поэтому может быть расширен на месте с помощью непосредственно следующего за ним realloc(). Все еще верно, даже если используется объединение по размеру - это не тот случай, когда определенные диапазоны памяти принадлежат только определенным размерам выделения. - person j_random_hacker; 06.01.2011
comment
... или если это так, то система по своей сути расточительна по памяти. Что касается перераспределения и сокращения, я не понимаю, как вызов malloc(), memcpy() и free() сам по себе менее подвержен фрагментации, чем вызов realloc(), поскольку AFAICT это именно то, что realloc() сделает сам, если он не может расширяться на месте. Могли бы вы объяснить? - person j_random_hacker; 06.01.2011
comment
На самом деле, то, не оставляет ли стекообразная последовательность выделений и освобождений пробелов, зависит от того, когда и как объединяются свободные блоки, поэтому я уступлю этому (при условии, что это определенно распространенный шаблон, поэтому система распределения это создало бы большую фрагментацию под ним, что было бы плохой системой). Но я хотел бы знать о сценарии с чрезмерным выделением и сокращением. - person j_random_hacker; 06.01.2011
comment
Предположим, у вас есть (помимо вершины кучи, которую мы для простоты проигнорируем) всего два свободных фрагмента A и B размером 1 КБ и 10 КБ соответственно, и вы хотите выделить 5 КБ и уменьшить его до 1 КБ. Распределение делит B пополам, и после уменьшения размера у вас остаются куски размером 1k и 9k. Если бы вы выделили всего 1 КБ для начала, у вас было бы все это в одном свободном фрагменте размером 10 КБ. Я называю это меньшей фрагментацией. То же самое применимо, если вы выполнили последовательность malloc/memcpy/free самостоятельно: в итоге у вас останется 10 000 свободных фрагментов. - person R.. GitHub STOP HELPING ICE; 06.01.2011
comment
Ясно спасибо. Я бы сказал, что вы с большей вероятностью увеличите фрагментацию с помощью этой стратегии, как если бы был только один свободный фрагмент (а именно, вершина кучи, что, вероятно, имело бы место, если бы только стек -подобное распределение/освобождение произошло до сих пор) malloc()+memcpy()+free() обязательно создает дыру (в вашем примере размером 5 КБ), а сжатие на месте - нет. - person j_random_hacker; 07.01.2011
comment
Действительно, выделение памяти — это очень сложная проблема, и никакая стратегия не может быть оптимальной для всех случаев и шаблонов использования. Я думаю, что вполне разумно предположить, что большинство программ, как правило, большую часть времени будут иметь несколько свободных фрагментов разного размера. - person R.. GitHub STOP HELPING ICE; 07.01.2011
comment
@R..: Если есть куски по 1 КБ и 10 КБ, выделение 5 КБ и сокращение до 1 КБ не будет таким же хорошим, как выделение 1 КБ для начала, но выделение 5 КБ и сокращение до 1,1 КБ оставит куски по 1 КБ и 8,9 КБ, по сравнению с 1K, 5K и 3,8K. Было бы еще лучше, если бы в стандартной библиотеке были определены некоторые подпрограммы для использования дескрипторов, поскольку они являются реальным ключом к восстановлению после фрагментации. - person supercat; 26.07.2015

Как насчет семьи malloc в целом? Подавляющее большинство больших долгоживущих программ, которые я видел, повсеместно используют динамическое выделение памяти, как если бы оно было бесплатным. Конечно, разработчики реального времени знают, что это миф, и небрежное использование динамического распределения может привести к катастрофическому увеличению использования памяти и/или фрагментации адресного пространства до точки исчерпания памяти.

В некоторых высокоуровневых языках без указателей машинного уровня динамическое выделение памяти не так уж плохо, потому что реализация может перемещать объекты и дефрагментировать память во время жизни программы, если она может поддерживать актуальность ссылок на эти объекты. Нетрадиционная реализация C тоже могла бы сделать это, но проработка деталей нетривиальна, и это потребовало бы очень значительных затрат на все разыменования указателя и сделало бы указатели довольно большими, поэтому для практических целей это невозможно в C.

Я подозреваю, что правильное решение для долгоживущих программ обычно состоит в том, чтобы выполнять свои небольшие рутинные выделения памяти, как обычно, с помощью malloc, но сохранять большие долгоживущие структуры данных в форме, в которой их можно периодически реконструировать и заменять для борьбы с фрагментацией, или в виде больших malloc блоков, содержащих ряд структур, составляющих одну большую единицу данных в приложении (например, презентация целой веб-страницы в браузере), или на диске с фиксированным размером кеша в памяти или памяти- сопоставленные файлы.

person Community    schedule 05.01.2011

С другой стороны, я никогда не понимал преимуществ atan(), когда есть atan2(). Разница в том, что atan2() принимает два аргумента и возвращает угол в диапазоне от -π..+π. Кроме того, он позволяет избежать ошибок деления на ноль и ошибок потери точности (деления очень маленького числа на очень большое или наоборот). Напротив, функция atan() возвращает только значение в диапазоне -π/2..+π/2, и вам нужно заранее выполнить деление (я не помню сценария, в котором atan() можно было бы использовать без деления). , за исключением простого создания таблицы арктангенсов). Предоставление 1,0 в качестве делителя для atan2() при задании простого значения не раздвигает границы.

person Community    schedule 03.01.2011
comment
atan( ) часто используется при выполнении определенных триггерных операций (но вы правы, что всегда где-то прячется неявный 1, и не мешало бы сделать его явным). - person Stephen Canon; 04.01.2011
comment
Однако я бы отметил одно преимущество atan( ): он примерно в 2 раза быстрее в хорошей математической библиотеке, потому что ему не нужно выполнять деление. Подозреваю, поэтому он и существует. - person Stephen Canon; 04.01.2011
comment
Иногда atan используется не для тригонометрии, а как приятная гладкая (фактически аналитическая) функция с желаемой монотонностью и граничными условиями. - person R.. GitHub STOP HELPING ICE; 04.01.2011

Другой ответ, так как они на самом деле не связаны, rand:

  • это неопределенное случайное качество
  • это не повторный вход
person Community    schedule 03.01.2011

Некоторые из этих функций изменяют некоторое глобальное состояние. (В Windows) это состояние разделяется на один поток — вы можете получить неожиданный результат. Например, первый вызов rand в каждом потоке даст один и тот же результат, и требуется некоторая осторожность, чтобы сделать его псевдослучайным, но детерминированным (для целей отладки).

person Community    schedule 03.01.2011

basename() и dirname() не являются потокобезопасными.

person Community    schedule 03.01.2011
comment
Это функции одного аргумента, которые изменяют свой аргумент. Это все равно, что сказать избегайте +=, потому что это не потокобезопасно. - person ; 04.01.2011
comment
Нет, они не потокобезопасны. Из справочной страницы: Функция basename() возвращает указатель на внутреннюю статическую память, которая будет перезаписана последующими вызовами. Функция может изменить строку, на которую указывает путь. - person arsenm; 04.01.2011
comment
Независимо от того, являются ли они потокобезопасными, basename и dirname не являются частью стандартной библиотеки C. - person Stephen Canon; 04.01.2011