Использовать функции с переменным числом аргументов в С89 без прохождения ряд аргументов, или последний аргумент?


Допустим, у меня есть функция с переменным числом аргументов foo(int tmp, ...), при вызове функции foo мне нужно знать, сколько аргументов есть. Я знаю два способа выяснить, сколько существует аргументов:

  1. Используйте последний аргумент при вызове foo, например -1, поэтому вызов функции будет выглядеть следующим образом: foo(tmp, 1, 2, 9, -1) и когда вы находитесь внутри foo и вызов va_arg возвращает -1, вы знаете, что прочитали все аргументы функции

  2. Добавьте еще один аргумент в foo, где программист будет иметь общее количество аргументов, так что у вас будут вызовы foo примерно так: foo(tmp, 5, 1, 2, 3, 4, 5) или foo(tmp, 2, 7, 8)

Раньше я шел по первому пути, и однажды у меня была следующая ошибка. С кодом:

expr_of_type(expr, boolexpr_e, newtable_e, nil_e, -1)

Где expr_of_type была вариадической функцией и проверяла, является ли expr(первый аргумент) одним из следующих типов (boolexpr_e или new_table_e или nil_e имели все типы перечисляемого типа). Я один случайно написал:

expr_of_type(expr, boolexpr_e, newtable_e, nil_e -1)

Я забыл запятую между nil_e и -1, потому что nil_e имел перечисляемый тип, nil_e-1 был допустимым выражением, и поскольку nil_e не был 0, данная вариадическая функция при попытке получить аргументы expr_of_type не нашла -1 в качестве последнего аргумента и продолжила поиск, создав ошибку, которая заняла у меня некоторое время, чтобы выяснить.

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

В поисках a лучший способ использовать / создавать вариадические функции я нашелвариадические макросы , которые могут решить ошибку, возникшую у меня при использовании первого способа. Но вариадические макросы доступны для стандарта C99. Я ищу лучший способ, чтобы использовать/создавать функции с переменным числом аргументов в С89. Есть идеи?

3 2

3 ответа:

В общем случае, вы все равно должны каким-то образом передать счетчик аргументов, будь то через значение sentinel или через явный счетчик.

Тем не менее, вы можете решить свою проблему с часовым, сделав лучшего часового. Это одна из причин, почему макросы препроцессора, расширяющиеся до отрицательных констант, должны быть заключены в круглые скобки:
#define VARARG_SENTINEL (-1)

Тогда nil_e VARARG_SENTINEL выдаст ошибку компиляции.

Использование enum или const int тоже сработает:

enum { VARARG_SENTINEL = -1 };

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

Также всегда есть возможность избежать полностью вариативных параметров с помощью динамической структуры.

struct vararray {
   uint_t n;
   uint_t params[0];
};

void foo(int tmp, struct varray *pVA);

Можно даже комплексировать с помощью union структур различных размеров.

Когда-то у нас был встроенный контроллер с определенным API, где мы использовали такой подход, union фиксированного размера struct, который передавался обработчику событий. Это имело некоторые преимущества, поскольку можно было использовать определенные типы, и компилятор мог лучше проверять типы параметров функций потому что мы не должны забывать, что на вариадических функциях нет проверки типа параметров.

Если ваша компиляция C99, вы можете использовать вариадические макросы для предоставления переменных аргументов без необходимости передавать счетчик явно:

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

void _foo(size_t n, int xs[])
{
    for(int i=0 ; i < n ; i++ ) {
        int x = xs[i];
        printf("%d\n", x);
    }        
}

#define foo(arg1, ...) do {            \
   int _x[] = { arg1, __VA_ARGS__ };   \
   _foo(sizeof(_x)/sizeof(_x[0]), _x); \
} while(0)

int main()
{
    foo(1, 2, 3, 4);
    foo(-1, -2, -3, -4, -5, -6, -7);
    return 0;
}

Вывод:

1
2
3
4
-1
-2
-3
-4
-5
-6
-7
Однако это не позволяет вам возвращать значение. Вы можете вернуть значение с расширениями gcc:
#include <stdio.h>
#include <stdarg.h>

int _foo(size_t n, int xs[])
{
    int i;
    for(i=0 ; i < n ; i++ ) {
        int x = xs[i];
        printf("%d\n", x);
    }        
    return n;
}

#define foo(arg1, ...) ({              \
   int _x[] = { arg1, __VA_ARGS__ };   \
   _foo(sizeof(_x)/sizeof(_x[0]), _x); \
})

int main()
{
    int x = foo(1, 2, 3, 4);
    printf("foo returned %d\n", x);
    x = foo(-1, -2, -3, -4, -5, -6, -7);
    printf("foo returned %d\n", x);
    return 0;
}

Вывод:

1
2
3
4
foo returned 4
-1
-2
-3
-4
-5
-6
-7
foo returned 7
Но, конечно, макросы мертвы. Да здравствуют макросы!

Правка:

Упс, недостаточно внимательно прочитал ОП. Прости!