Va_start (и т. Д.) Реентерабельный? - PullRequest
5 голосов
/ 05 октября 2010

При редактировании класса с длинной историей я столкнулся с особой привычкой архитектора заключать его последовательность va_start -> va_end в мьютекс.В журнале изменений для этого дополнения (которое было сделано около 15 лет назад и с тех пор не пересматривалось) отмечалось, что это произошло потому, что va_start et.все не было реентерабельным.

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

В частности, рассматриваемая функция выглядит примерно так:

void write(const char *format, ...)
{
    mutex.Lock();
    va_list args;
    va_start(args, format);
    _write(format, args);
    va_end(args);
    mutex.Unlock();
}

Это вызывается из нескольких потоков.

Ответы [ 3 ]

6 голосов
/ 05 октября 2010

Что касается последовательного повторного входа (т. Е. Если foo() использует va_start, безопасно ли для foo() вызывать bar(), который также использует va_start), ответ будет хорошим - до тех пор, покакак экземпляр va_list не то же самое.Стандарт гласит:

Ни макрос va_start, ни макрос va_copy не должны вызываться для повторной инициализации ap без промежуточного вызова макроса va_end для того же ap.

Итак, вывсе в порядке, пока используется другой va_list (упоминаемый выше как ap).

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

На более популярных платформах, пока в макрос va_start передается другой аргумент va_list, у вас не должно возникнуть проблем с несколькими потоками, проходящими через один и тот же va_start.А поскольку аргумент va_list обычно находится в стеке (и поэтому разные потоки будут иметь разные экземпляры), вы обычно имеете дело с разными экземплярами va_list.

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

2 голосов
/ 05 октября 2010

Что ж, способ доступа к переменным аргументам реализован в C, делает довольно очевидным, что объекты va_list хранят некоторое внутреннее состояние . Это делает его не реентерабельным, что означает, что вызов va_start для va_list объекта аннулирует эффект предыдущего va_start. Но еще точнее, C явно запрещает снова вызывать va_start на va_list объекте перед «закрытием» ранее вызванного va_start сеанса с va_end.

Объект va_list предполагается использовать «неперекрывающимся» образом: va_start...va_end. После этого вы можете сделать еще один va_start для того же объекта va_list. Но попытка перекрытия сеансов va_start...va_end на одном и том же объекте va_list не будет работать.

P.S. На самом деле теоретически возможно реализовать некоторое внутреннее состояние на основе LIFO в любом итераторе на основе сеанса. То есть теоретически возможно разрешить вложенные va_start...va_end сеансы на одном и том же va_list объекте (что делает его реентерабельным в этом смысле). Но спецификация библиотеки C не предоставляет ничего подобного.

Обратите внимание, что в C99 va_list объекты могут копироваться va_copy. Таким образом, если вам нужно просмотреть один и тот же список аргументов несколькими перекрывающимися сеансами va_start...va_end, вы всегда можете добиться этого, создав несколько независимых копий исходного va_list.

P.P.S. Глядя на предоставленный вами пример кода ... В этом случае абсолютно нет необходимости в мьютексах (что касается целостности va_list). И нет необходимости в реентерабельном va_list объекте. Ваш код прекрасно работает без мьютексов. Это будет хорошо работать в многопоточной среде. Макросы из группы va_... не работают с фактическим «указателем стека». Вместо этого они работают с полным независимым объектом va_list, который можно использовать для перебора значений, хранящихся в стеке. Вы можете думать об этом как о своем собственном, частном локальном указателе стека. Каждый поток, вызывающий вашу функцию, получит свою копию этого va_list, итерируясь по своему стеку. Между потоками не будет конфликта.

0 голосов
/ 05 октября 2010

Есть некоторые платформы, где va_list может иметь проблемы с повторным входом, но на этой же платформе все локальные переменные имеют такие проблемы. Но мне любопытно: что ожидает ваша функция _write? Если он использует параметры, которые установлены перед вызовом write, это само по себе может вызвать проблемы с потоками, если только (1) какой-либо конкретный экземпляр объекта, содержащий _write, не будет использоваться только одним потоком за раз, или (2) все потоки, использующие объект для _write, будут нуждаться в тех же параметрах настройки.

...