2015-06-23 4 views
5

Я хотел бы создать функцию, которая принимает переменное количество аргументов, причем один из этих аргументов сам является va_list; но что-то не получается в моем коде, и я не понимаю, что ...Поместите переменную va_list внутри ... список переменных аргументов (!)

ПРЕДУПРЕЖДЕНИЕ. Мой вопрос заключается не в разработке кода, выполняющего то, что я хочу сделать (я нашел способ обойти проблему), но только о понимании того, что я сделал неправильно ...

Чтобы объяснить мой вопрос, давайте начнем с простого примера, а именно: функция ffprintf, которая действует как fprintf, но писать его содержимое на несколько строк, строк, число которых указанный первым аргументом ffprintf, и тождества которого задаются самыми следующими аргументами (число этих аргументов может варьироваться от одного вызова к другому, поэтому вам нужно использовать переменную argum ent список). Такая функция будет использоваться следующим образом:

FILE *stream0, *stream1, *stream2; 
int a, b; 
ffprintf (3, stream0, stream1, stream2, "%d divided by %d worths %f", a, b, (double)a/b); 

И его код будет:

void ffprintf (int z, ...) 
{va_list vlist, auxvlist; 
    FILE **streams = malloc (z * sizeof(FILE *)); 
    va_start (vlist, z); 
    for (int i = 0; i < z; ++i) 
    {streams[i] = va_arg (vlist, FILE *); // Getting the next stream argument 
    } 
    char const *format = va_arg (vlist, char const *); // Getting the format argument 
    for (int i = 0; i < z; ++i) 
    {va_copy (auxvlist, vlist); // You have to work on a copy "auxvlist" of "vlist", for otherwise "vlist" would be altered by the next line 
    vfprintf (streams[i], format, auxvlist); 
    va_end (auxvlist); 
    } 
    va_end (vlist); 
    free (streams); 
} 

Это работает отлично. Теперь, есть также стандартная функция vfprintf, прототипом которого является vfprintf (FILE *stream, char const* format, va_list vlist);, и который вы используете, как это, чтобы создать другую функцию, имеющую переменный список аргументов:

void fprintf_variant (FILE *stream, char const* format, ...) 
{ 
    va_list vlist; 
    va_start (vlist, format); 
    vfprintf (stream, format, vlist); 
    va_end (vlist); 
} 

Это работает тоже хорошо. Теперь моя цель состоит в том, чтобы объединить обе идеи, чтобы создать функцию, которую я бы назвал vffprintf, который вы будете использовать, как это:

FILE *stream0, *stream1, *stream2; 
void fprintf_onto_streams012 (char const *format, ...) 
{va_list vlist; 
    va_start (vlist, format); 
    vffprintf (3, stream0, stream1, stream2, format, vlist); 
    va_end (vlist); 
} 

Я разработал следующий код:

void vffprintf (int z, ...) 
{va_list vlist, auxvlist, auxauxvlist; 
    va_start (vlist, z); 
    FILE **streams = malloc (z * sizeof(FILE *)); 
    for (int i = 0; i < z; ++i) 
    {streams[i] = va_arg (vlist, FILE *); 
    } 
    char const *format = va_arg (vlist, char const *); 
    va_copy (auxvlist, va_arg (vlist, va_list)); // Here I get the next argument of "vlist", knowing that this argument is of "va_list" type 
    for (int i = 0; i < z; ++i) 
    {va_copy (auxauxvlist, auxvlist); 
    vfprintf (streams[i], format, auxvlist); 
    va_end (auxauxvlist); 
    } 
    va_end (auxvlist); 
    va_end (vlist); 
    free (streams); 
} 

Этот код компилируется без сучка, но он не работает должным образом ... например, если я пишу следующий код: полный

#include <stdlib.h> 
#include <stdio.h> 
#include <stdarg.h> 

void vffprintf (int z, ...) 
{va_list vlist, auxvlist, auxauxvlist; 
    FILE **streams = malloc (z * sizeof(FILE *)); 
    va_start (vlist, z); 
    for (int i = 0; i < z; ++i) 
    {streams[i] = va_arg (vlist, FILE *); 
    } 
    char const *format = va_arg (vlist, char const *); 
    va_copy (auxvlist, va_arg (vlist, va_list)); 
    for (int i = 0; i < z; ++i) 
    {va_copy (auxauxvlist, auxvlist); 
    vfprintf (streams[i], format, auxauxvlist); 
    va_end (auxauxvlist); 
    } 
    va_end (auxvlist); 
    va_end (vlist); 
    free (streams); 
} 

void printf_variant (char const *format, ...) 
{va_list vlist; 
    va_start (vlist, format); 
    vffprintf (1, stdout, format, vlist); 
    va_end (vlist); 
} 

int main (void) 
{printf_variant ("Ramanujan's number is %d.\n", 1729); 
    return 0; 
} 

Я получаю Segfault ... Почему ?!!

P.-S .: Извините за этот очень длинный вопрос; но я хотел, чтобы это было совершенно ясно, потому что это довольно технически ...

P.-S.2: Я использовал намеренно оба тега «va-list» и «alternargumentlists» для этого вопроса, потому что меня интересует is va_list, рассматривается как тип, внутри (другой) список переменных аргументов, рассматривается как список ... Так что это действительно две разные концепции.

+0

Итак, вопрос в том, почему последний пример не работает? – this

+0

Да, это именно то, что :-) –

+0

Выглядит хорошо, если вы игнорируете утечку памяти. – this

ответ

2

Описание va_arg в окончательном проекте C11 (N1570) содержит (тип является второй аргумент):

если тип не совместим с типа из фактического следующего аргумент (как повышен в соответствии с умолчанию аргумент поощрений), поведение не определено

va_list позволено быть типом массива (й e требует, чтобы это был так называемый «полный тип объекта»), и, похоже, ваша реализация использует эту возможность. Вероятно, вы знаете, что в массивах C нельзя передавать аргументы, поскольку они распадаются на указатели, а тип такого указателя несовместим с исходным типом массива.

Например: int * не совместимо с int [1]. Поэтому, если вам действительно необходимо передать массив или va_list с возможностью порта, определите struct с участком va_list и передайте его (см. Why can't we pass arrays to function by value?).

+0

Здравствуйте, cremno, Я не понял все детали, но у меня сложилось впечатление, что ваша точка связана со следующим предложением из стандарта: «Аргумент' type' - это имя типа, указанное так, чтобы тип указателя к объекту, который имеет указанный тип, можно получить просто добавив '*' to 'type'. Но ничто не гарантирует, что 'va_list' будет таким типом, как, например, тип массива не будет работать ... Правильно ли это? С другой стороны, это, похоже, не объясняет, как трюк «пустоты» Эрика Цуй работал ... –

+1

@ Нэнси-Н: Это ** трюк **. Но он должен объяснить, что это часть его ответа. Это, вероятно, будет работать везде, где бы вы ни заботились. Это не меняет того факта, что это UB (описание, опубликованное им, также ошибочно). [Здесь] (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=50581) - отчет об ошибке GCC, чей репортер хочет сделать что-то похожее или даже то же, что и вы. Он даже упоминает кромку края в C90! Может быть, чтение будет немного легче, чем мой ответ. В нем также упоминается использование структуры как возможного решения. – cremno

+0

@cremno, спасибо за исправление. Таким образом, я сделал больше исследований для вещей о 'va_list' и' va_list * 'под другой реализацией va_list, это действительно проблема. –

1
void vffprintf (int z, ...) 
{ 
    //... 
    va_copy (auxvlist, va_arg (vlist, va_list));//this line has the problem 
    //... 
} 

Просто быстрый и хитрый способ, как это, он будет работать.

void vffprintf (int z, ...) 
{ 
    //... 
    va_copy (auxvlist, va_arg (vlist, void*)); 
    //... 
} 

Вот некоторые упоминания о var_arg и va_list, которые должны были обеспечить детальное и тщательное объяснение.

1) Pass va_list or pointer to va_list?

2) Is GCC mishandling a pointer to a va_list passed to a function?

3) What is the format of the x86_64 va_list structure?

Надежда они полезны.

+1

Это действительно работает; потрясающий!:-D Если у вас есть более подробное объяснение, я очень хочу его услышать: почему предыдущая строка не удалась? Почему новый работает? Это этот «пустой трюк» переносится? ... –

+0

Мне остается неясным. Когда 'printf_variant' вызывает' vffprintf', список аргументов переменной для 'vffprintf' является {' stdout', 'format',' vlist_0'}, а 'vlist_0' является списком переменных аргументов' printf_variant', а именно {'1929 '}. Таким образом, при вызове 'va_arg (vlist_1, va_list)' (я поставил суффиксы '_0' и' _1', чтобы сделать разницу между как 'vlist' clear), следующий аргумент' vlist_1' - {'1929'} который конкретно может быть «struct {char *, int}». Но это не int, не так ли? ... И вообще, как это может быть преобразовано в 'void *' ?! Извините за то, что вы не поняли более легко ... –

+1

@ Nancy-N, что я пытался сделать, это что-то вроде этого: va_copy (auxvlist, va_arg (vlist, va_list *)) ', но он дает предупреждение. Итак, я меняю его на '' va_copy (auxvlist, va_arg (vlist, void *)) '' –

1

Вам может понадобиться, чтобы обернуть типа va_list в структуры, если вы хотите получить его с помощью va_arg():

#include <stdlib.h> 
#include <stdio.h> 
#include <stdarg.h> 

typedef struct 
{ 
    va_list list ; 
} my_list ; 

void vffprintf (int z, ...) 
{my_list vlist, auxvlist, auxauxvlist; 
    FILE **streams = malloc (z * sizeof(FILE *)); 
    va_start (vlist.list, z); 
    for (int i = 0; i < z; ++i) 
    {streams[i] = va_arg (vlist.list, FILE *); 
    } 
    char const *format = va_arg (vlist.list, char const *); 
    my_list parent = va_arg (vlist.list, my_list) ; 
    va_copy (auxvlist.list, parent.list); 
    for (int i = 0; i < z; ++i) 
    {va_copy (auxauxvlist.list, auxvlist.list); 
    vfprintf (streams[i], format, auxauxvlist.list); 
    va_end (auxauxvlist.list); 
    } 
    va_end (auxvlist.list); 
    va_end (vlist.list); 
    free (streams); 
} 

void printf_variant (char const *format, ...) 
{my_list vlist; 
    va_start (vlist.list, format); 
    vffprintf (1, stdout , format, vlist); 
    va_end (vlist.list); 
} 

int main (void) 
{printf_variant ("Ramanujan's number is %d.\n", 1729); 
    return 0; 
} 

Проблема связана с тем, что массив и указатель того же типа не совместим, а va_list определяется как массив. Затем вы пытаетесь получить этот тип:

va_arg (vlist, va_list) 

Так вы говорите va_arg вы получаете массив, но если факт пропущенный va_list заглох на указатель. Вы должны использовать версию указателя va_list, но вы не знаете реального определения va_list, в первую очередь, так что вы не можете получить версию указателя.

Решение состоит в том, чтобы обернуть va_list в тип, которым вы управляете, структурой.

+0

@ this - Спасибо за этот способ обхода проблемы (чего я не знал) и за то, что записал код! :-) –

+0

@ Nancy-N Так этот код действительно исправить проблему? – this

+0

@ это - это действительно было - по крайней мере, на моей собственной машине ;-) –

Смежные вопросы