Есть ли способ пометить использование нереентерабельных вызовов библиотеки C?

Я работаю над многопоточным проектом, и мне было интересно, есть ли способ, чтобы компилятор пометил использование не реентерабельных вызовов библиотеки C (например, strtok intsead из strtok_r)? Если нет, есть ли список вызовов, которые не реентерабельны, чтобы я мог периодически просматривать свою базу кода с помощью grep?

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

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


person Ravi    schedule 24.06.2011    source источник
comment
Вопрос - все ли вызовы времени выполнения C в Linux, которые поддерживают состояние между вызовами (например, malloc, rand, strtok и т. Д.), По своей сути не являются потокобезопасными? Или существует директива компилятора / компоновщика для указания связывания с поточно-безопасной версией для этих вызовов? Мне действительно интересно, действительно ли есть проблема, которую нужно решить для обеспечения безопасности потоков.   -  person selbie    schedule 24.06.2011
comment
@selbie: Нет, все по-разному. Например, malloc обычно является потокобезопасным. В любом случае, безопасность потока rand() - это философский вопрос. Идеальная реализация rand (оракул) по своей сути была бы потокобезопасной.   -  person MSalters    schedule 25.06.2011


Ответы (5)


Что касается источника, вы могли бы настоять на том, чтобы каждый исходный файл содержал строку:

#include <beware.h>

после заголовков C, а затем файл заголовка beware.h содержит:

#define strtok   unsafe_function_call_detected_strtok
#define getenv   unsafe_function_call_detected_getenv

или какой-то другой подходящий набор имен, которые вряд ли будут настоящими функциями. Это приведет к ошибкам компиляции и / или компоновщика.

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

Это не будет делать компилятор, но его достаточно легко включить в сценарии сборки. См. Следующую стенограмму:

$ cat qq.c
    #include <stdio.h>

    int main (int argc, char *argv[]) {
        printf ("Hello, world.\n");
        return 0;
    }

$ gcc -c -o qq.o qq.c

$ nm qq.o
00000000 b .bss
00000000 d .data
00000000 r .rdata
00000000 t .text
         U ___main
00000000 T _main
         U _puts

Вы можете увидеть неразрешенные символы в этом выводе с помощью маркера Ugcc очень тайно решил использовать puts вместо printf, поскольку я дал ему постоянную строку без команд форматирования).

person paxdiablo    schedule 24.06.2011
comment
Хорошая идея для файла заголовка и сценария nm. Есть ли где-нибудь список нереентерабельных функций C lib? - person Ravi; 24.06.2011
comment
Если вы используете GCC, вы можете использовать #pragma GCC poison strtok как лучшую альтернативу #define. - person Matthew Slattery; 24.06.2011
comment
В качестве варианта стиля nm вы можете определить такой файл: checkfns.c: void not_thread_safe(); void puts() { not_thread_safe(); } Тогда он скроет системные версии функций, и вы получите сообщение об ошибке типа: Undefined symbols: "_not_thread_safe", referenced from: _puts in checkfns.o, если вы попытаетесь использовать путы. (Вам нужно будет включить удаление мертвого кода для случаев, которые его не используют ... -Xlinker -dead_strip для меня) - person Michael Anderson; 24.06.2011
comment
Если вы используете #define, используйте имя типа unsafe_function_call_detected, чтобы сообщения об ошибках компилятора включали что-то, что говорит вам, что на самом деле не так. - person Karl Knechtel; 24.06.2011
comment
@ Карл: ooga-booga-wtf недостаточно ясно? :-) Нет, серьезно, это хорошее предложение, я обновлю ответ. - person paxdiablo; 24.06.2011
comment
@ Майкл, твоя идея интересна, но я не могу заставить ее работать. Сообщите мне, если это сработает для вас. Я использую GCC 4.5.2. - person Ravi; 24.06.2011
comment
@suravi У меня это определенно работает .. Я поместил здесь код, который использую: gist.github.com/1044114 - Я нахожусь в OS X, использую gcc 4.0.1 - person Michael Anderson; 24.06.2011
comment
@suravi Похоже, что опция компоновщика для удаления мертвой функции отличается на вашей платформе (это опция -dead_strip). Вам нужно будет изменить ее на то, что использует ваша платформа. (человек gcc или человек ld должен найти это для вас) - person Michael Anderson; 24.06.2011
comment
Спасибо, поищу аналогичный вариант на Ubuntu. - person Ravi; 24.06.2011

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

Я просмотрел список функций GNU libc и выбрал те, которые содержат _r. Вот список.

asctime, крипта, ctime, drand48, ecvt, зашифровать, erand48, fcvt, fgetgrent, fgetpwent, getdate, getgrent, getgrgid, getgrnam, gethostbyaddr, gethostbyname2, gethostbyname, getmntent, getnetgrent, getwnpwent, getntpwent, getntent, getnetgrent, getwnpwent, getwnam gmtime, hcreate, hdestroy, hsearch, initstate, jrand48, lcong48, lgamma, lgammaf, lgammal, localtime, lrand48, mrand48, nrand48, имя точки, qecvt, qfcvt, rand, random, readdir64, readdir, seed48, setkey, setkey, set srandom, strerror, strtok, tmpnam, ttyname

person Ravi    schedule 26.06.2011
comment
Примечание по readdir_r: посмотрите справочную страницу. GNU libc рекомендует использовать обычный readdir и исключить readdir_r - person Dacav; 06.12.2017

Обращаясь ко второй части вашего вопроса:

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

person Michael Anderson    schedule 24.06.2011

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

Во время разработки вы также можете использовать valgrind, чтобы сделать то же самое.

Примеры кода и ссылки см. В ответах на как я могу перехватывать системные вызовы linux ?

person Nemo    schedule 24.06.2011

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

Список функции Cppcheck будут отмечать.

Пример сообщения Cppcheck:

Вызывается нереентерабельная функция strtok. Для потоковобезопасных приложений рекомендуется использовать функцию повторной замены 'strtok_r'. (переносимость: nonreentrantFunctionsstrtok)

person Colin D Bennett    schedule 08.10.2014
comment
Список проверенных функций кажется неполным. strerror_r () отсутствует. Есть также другие функции (без суффикса _r), которые не реентерабельны, такие как getenv (), system () и многие другие. Перечислите здесь: [pubs.opengroup.org/onlinepubs/f9699919799/ /. Но и это не все. Подумайте о setlocale () или любой другой функции, которая изменяет атрибут всего процесса. Вы не можете вызывать их из многопоточных библиотек, не вызывая хаоса. - person Michi Henning; 12.11.2014