Каков правильный способ создания поточно-безопасной многоплатформенной библиотеки C?

Рассмотрим следующую тривиальную C-программу,

#include <errno.h>
int
main(int argc, char* argv[]) {
  return errno;
}

При компиляции в Solaris поведение этого кода зависит от наличия -D_REENTRANT.

solaris$ cc -E test.c | grep return
  return errno;
solaris$ cc -D_REENTRANT -E test.c | grep return
  return  ( * ( ___errno ( ) ) );

причем последняя версия является потокобезопасной. Если мы скомпилируем тот же код в Linux, мы получим такое же поведение, которое не зависит от -D_REENTRANT

linux$ gcc -E test.c | grep return
  return (*__errno_location ());
linux$ gcc -D_REENTRANT -E test.c | grep return
  return (*__errno_location ());

Solaris 'cc имеет опцию -mt, что подразумевает -D_REENTRANT, как и gcc -pthread. Однако для библиотеки указание этих многопоточных параметров кажется неудовлетворительным, поскольку оно вводит ненужную зависимость от времени выполнения потоковой передачи. Однако, если библиотека должна быть потокобезопасной (включая errno), тогда семантика, защищенная потоками, необходима как во время компиляции библиотеки, так и кода вывода. В Linux это легко, потому что errno всегда является локальным потоком, но не гарантируется на других системах, как только что было продемонстрировано.

В результате возникает вопрос: как правильно скомпилировать и распределить потокобезопасную библиотеку с заголовками? Один из вариантов был бы в #define _REENTRANT в главном заголовке, но это вызовет проблемы, если #include <errno.h> произойдет до включения заголовка библиотеки. Другой вариант - скомпилировать библиотеку с -D_REENTRANT и иметь главный заголовок #error, если _REENTRANT не определен.

Какой правильный/лучший способ создать потокобезопасную библиотеку и обеспечить ее правильное взаимодействие с кодом, с которым он связан?

Ответ 1

В настоящий момент у меня нет доступа к какой-либо машине Solaris, поэтому я не могу это проверить. Но что происходит, когда вы ставите #define _POSIX_C_SOURCE 200112L как самую первую строку в test.c (до включения <errno.h>)? Если ваш Solaris совместим с POSIX, то это должно сделать errno развернуть до потоковой версии. Это связано с тем, что POSIX определяет errno, как показано ниже:

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

Соответственно, это переносимо для любой POSIX-совместимой системы. На самом деле, если вы хотите написать POSIX-совместимый код приложения, вы всегда должны определять _POSIX_C_SOURCE значение, соответствующее минимальной версии POSIX, на которую вы нацеливаете. Определение должно быть в верхней части каждого исходного файла, прежде чем включать любые заголовки. Из версии стандарта 2001 года:

Строго совместимое приложение POSIX - это приложение, которое требует только средств, описанных в IEEE Std 1003.1-2001. Такое приложение:

...

8. Для языка программирования C должен определяться _POSIX_C_SOURCE как 200112L перед включением любого заголовка

Ответ 2

Если ваша библиотека использует autoconf, вы, вероятно, захотите использовать макрос AC_USE_SYSTEM_EXTENSIONS. Этот макрос устанавливает некоторые специфичные для конкретной задачи определения, которые позволяют использовать семантику расширений POSIX+. У меня нет системы Solaris для тестирования на данный момент, но я считаю, что _POSIX_PTHREAD_SEMANTICS должен включить безопасное для потока errno. По крайней мере, он позволяет использовать функции POSIX _r(), а не варианты POSIX-draft _r(), которые Solaris досадно предоставляет по умолчанию.