va_list и Segmentation Fault

Vladimir
Опубликовано в: C/C++

Весьма неочевидная ошибка

Недавно пришлось вспомнить молодость и программирование на C. В результате столкнулся с одной очень неочевидной ошибкой.

Те, кто программируют под Linux/UNIX, вероятно, знают о функции vsyslog. Её приятной особенностью является то, что вместо переменного количества аргументов она берет фиксированное количество, последним из которых является аргумент типа va_list. Недостаток функции — она не входит в стандарты POSIX (то есть в коде её можно использовать на свой страх и риск — и не забыть добавить проверку на её существование в autoconf).

Возникла необходимость написать несколько функций-обёрток для syslog: функция принимает произвольное количество параметров (форматная строка и аргументы), выполняет некие действия и вызывает syslog().

Проблема в том, что функции с произвольным число аргументов очень тяжело вызвать другую функцию с произвольным числом аргументов. Поэтому vsyslog() был бы как нельзя кстати. Но нам нужно решение, работающее всегда :-)

Начнём с функции-обёртки (я оставляю только нужный код):

[-]
View Code C
void notice(const char* fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    log_message(LOG_NOTICE, fmt, ap);
    va_end(ap);
}

Функция log_message() имеет следующий прототип:

[-]
View Code C
static int log_message(int level, const char* fmt, va_list args);

Для формирования готового сообщения в log_message() кажется логичным использование vsnprintf():

[-]
View Code C
static int log_message(int level, const char* fmt, va_list args)
{

    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% случаев можно наблюдать такую картину:

[-]
View Code Text
executor[23431]: [DEBUG] Worker 23431[23429]: forked, hello from child
executor[23429]: [DEBUG] Worker 23429: program exit status: 0000 (0)
executor[23429]: [DEBUG] Worker -643338864: `����
executor[23429]: [DEBUG] Worker 23429 exits

В некоторых случаях, когда передаётся строка (%s), случаются странные вещи, что отражено в логе. Я не проверил, происходит ли это в случае, если цикл while() выполняется больше одного раза, но подозреваю, что это так.

А теперь правильный вариант:

[-]
View Code C
static int log_message(int level, const char* fmt, va_list args)
{

    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> можно найти решение…

Добавить в закладки

Связанные записи

3
Март
2009

Комментарии к статье «va_list и Segmentation Fault»  »

К статье «va_list и Segmentation Fault» комментариев пока нет. Не хотите ли стать первым?

Подписаться на RSS-ленту комментариев к статье «va_list и Segmentation Fault» Trackback URL: http://blog.sjinks.org.ua/c-cpp/507-va_list-and-segmentation-fault/trackback/

Оставить комментарий к записи «va_list и Segmentation Fault»

Вы можете использовать данные тэги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Оставляя комментарий, Вы выражаете своё согласие с Правилами комментирования.

Подписаться, не комментируя