va_arg возвращает неверный аргумент - PullRequest
2 голосов
/ 02 июля 2010

С помощью следующего кода va_arg возвращает мусор для второго и третьего прохода через vProcessType.

// va_list_test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <tchar.h>
#include <cstdarg>
#include <windows.h>

void processList(LPTSTR str, ...);
void vProcessList(LPTSTR str, va_list args);
void vProcessType(va_list args, int type);

int _tmain(int argc, _TCHAR* argv[])
{
    LPTSTR a = TEXT("foobar");
    int b = 1234;
    LPTSTR c = TEXT("hello world");
    processList(TEXT("foobar"), a, b, c);
    return 0;
}

void processList(LPTSTR str, ...)
{
    va_list args;
    va_start(args, str);
    vProcessList(str, args);
    va_end(args);
}
void vProcessList(LPTSTR str, va_list args)
{
    vProcessType(args, 1);
    vProcessType(args, 2);
    vProcessType(args, 1);
}

void vProcessType(va_list args, int type)
{
    switch(type)
    {
    case 1:
        {
            LPTSTR str = va_arg(args, LPTSTR);
            printf("%s", str);
        }
        break;
    case 2:
        {
            int num = va_arg(args, int);
            printf("%d", num);
        }
        break;
    default:
        break;
    }
}

Не разрешено ли таким образом передавать объект va_list? Первый вызов va_arg внутри vProcessType возвращает ожидаемую строку. Во второй и третий раз эта функция возвращает указатель на начало первой строки вместо ожидаемых значений.

Если я поднял вызов va_arg на vProcessList, все, кажется, работает нормально. Только когда я передаю va_list через функцию, я получаю такое поведение.

Ответы [ 4 ]

5 голосов
/ 02 июля 2010

Вы передаете один и тот же va_list каждый раз vProcessType() - при каждом вызове vProcessType() вы действуете на первую va_arg в списке.

Таким образом, вы всегда имеете дело с параметром TEXT("foobar") при вызове vProcessType().

Также обратите внимание, что в стандарте сказано, что передать va_list другой функции:

Объект ap [типа va_list] может быть передан в качестве аргумента другой функции; если эта функция вызывает макрос va_arg с параметром ap, значение ap в вызывающей функции является неопределенным и должно быть передано макросу va_end перед любой дальнейшей ссылкой на ap.

Сноска в стандарте указывает, что все в порядке, чтобы передать указатель на va_list, поэтому вы можете vProcessType() взять указатель на va_list:

void vProcessType(va_list* pargs, int type);
4 голосов
/ 02 июля 2010

Когда вы передаете объект va_list другой функции и эта другая функция использует va_arg для соответствующего параметра, то va_list будет иметь неопределенное значение в вызывающей функции, когда элемент управления вернется. Единственное, что вам разрешено сделать, - это применить va_end к этому va_list объекту.

Так указано в стандарте (7.15 / 3)

Если доступ к переменным аргументам желательно, вызываемая функция должна объявить объект (обычно упоминается как ap в этом подпункте), имеющий введите va_list. Объект ap может быть передан в качестве аргумента другому функция; если эта функция вызывает va_arg макрос с параметром ap, значение ap в вызывающей функции неопределенный и должен быть передан макрос va_end до дальнейшего ссылка на ap.

Не передавать va_list объекты по значению . Если ваше намерение состоит в том, чтобы продолжить анализ аргумента в каждой последующей подфункции, то вам нужно передать va_list объект по указателю .

Если вы действительно хотите передать свой va_list объект по значению , т. Е. Если вы хотите, чтобы каждая подфункция выполняла синтаксический анализ с одной и той же точки, вы должны заранее скопировать объект va_list вручную используя макрос va_copy. («Заранее» означает, что вам нужно сделать столько копий, сколько вам нужно, прежде чем какая-либо подфункция сможет сделать на ней va_arg.)

3 голосов
/ 02 июля 2010

Вы передаете свой va_list по значению. Попробуйте вместо этого передать указатель на одно значение (или, если вам нужна версия только для C ++, вы можете использовать ссылку):

void vProcessList(LPTSTR str, va_list args)
{
    vProcessType(&args, 1);
    vProcessType(&args, 2);
    vProcessType(&args, 1);
}

void vProcessType(va_list *args, int type)
{
    ...
    LPTSTR str = va_arg(*args, LPTSTR);
    ...
    int num = va_arg(*args, int);
}
1 голос
/ 03 июля 2010

Как уже отмечали другие, стандарт C99 позволяет переносить (среди реализаций C99) программу для выполнения через va_list более одного раза.Нет хорошего переносимого способа сделать это в реализациях до C99.То, что я делал, когда мне нужно было пройти через список printf более одного раза (для функции «центральная строка», которая должна была оценить аргументы один раз, чтобы определить их ширину, а затем во второй раз фактически отобразить их), былоизучите "stdarg.h" поставщика компилятора и выдумайте мою собственную реализацию необходимой функциональности.

Если кто-то хотел действительно переносимую реализацию, которая бы работала на более ранних компиляторах C, и если бы заранее было известно максимальное количествопроходы, которые потребуются, я думаю, можно создать массив объектов va_ptr, использовать va_start для всех из них, а затем передать массив в дочернюю процедуру.Сорта неприглядная.

...