va_list и Segmentation Fault
Весьма неочевидная ошибка
Недавно пришлось вспомнить молодость и программирование на C. В результате столкнулся с одной очень неочевидной ошибкой.
Те, кто программируют под Linux/UNIX, вероятно, знают о функции vsyslog. Её приятной особенностью является то, что вместо переменного количества аргументов она берет фиксированное количество, последним из которых является аргумент типа va_list. Недостаток функции — она не входит в стандарты POSIX (то есть в коде её можно использовать на свой страх и риск — и не забыть добавить проверку на её существование в autoconf).
Возникла необходимость написать несколько функций-обёрток для syslog: функция принимает произвольное количество параметров (форматная строка и аргументы), выполняет некие действия и вызывает syslog().
Проблема в том, что функции с произвольным число аргументов очень тяжело вызвать другую функцию с произвольным числом аргументов. Поэтому vsyslog() был бы как нельзя кстати. Но нам нужно решение, работающее всегда
Начнём с функции-обёртки (я оставляю только нужный код):
{
va_list ap;
va_start(ap, fmt);
log_message(LOG_NOTICE, fmt, ap);
va_end(ap);
}
Функция log_message() имеет следующий прототип:
Для формирования готового сообщения в log_message() кажется логичным использование vsnprintf():
{
int n;
int size = 1024;
char *p;
char *np;
const char* x = level_to_str(level);
p = malloc(size);
if (NULL == p) {
return -1;
}
while (1) {
n = vsnprintf(p, size, fmt, args);
if (n > -1 && n < size) {
syslog(level, "[%s] %s", x, p);
free(p);
return 0;
}
if (n > -1) {
size = n + 1;
}
else {
size <<= 1;
}
np = realloc(p, size);
if (NULL == np) {
free(p);
return -1;
}
else {
p = np;
}
}
}
Пример с использованием vsnprintf() взят с небольшими изменениями отсюда.
В 99.9% случаев код работает!
Но в 0.1% случаев можно наблюдать такую картину:
executor[23429]: [DEBUG] Worker 23429: program exit status: 0000 (0)
executor[23429]: [DEBUG] Worker -643338864: `����
executor[23429]: [DEBUG] Worker 23429 exits
В некоторых случаях, когда передаётся строка (%s), случаются странные вещи, что отражено в логе. Я не проверил, происходит ли это в случае, если цикл while() выполняется больше одного раза, но подозреваю, что это так.
А теперь правильный вариант:
{
int n;
int size = 1024;
char *p;
char *np;
const char* x = level_to_str(level);
p = malloc(size);
if (NULL == p) {
return -1;
}
va_list copy;
while (1) {
va_copy(copy, args);
n = vsnprintf(p, size, fmt, copy);
va_end(copy);
if (n > -1 && n < size) {
syslog(level, "[%s] %s", x, p);
free(p);
return 0;
}
if (n > -1) {
size = n + 1;
}
else {
size <<= 1;
}
np = realloc(p, size);
if (NULL == np) {
free(p);
return -1;
}
else {
p = np;
}
}
}
va_copy меняет всё!
Параллельно возникает глупый вопрос: va_copy появилось только в C99, как без него обходились раньше? Хотя, возможно, в <varargs.h> можно найти решение…
Мар
2009
Комментарии к статье «va_list и Segmentation Fault» »
Пожалуйста, не используйте эту форму для комментирования! Данная форма предназначена исключительно для ботов.
Оставить комментарий к записи «va_list и Segmentation Fault»
गते गते पारगते पारसंगते बोधि स्वाहा
Меня зовут Владимир, я программист-фрилансер, специализирующийся на Web-программировании и програмировании под Linux.
По совместительству занимаюсь администрированием LAMP/LNMP-серверов и техническим переводом.

