va_list с неизвестными именами типов - известен только размер байта! - PullRequest
5 голосов
/ 09 июля 2010

У меня есть вопрос программирования на C: я хочу написать функцию со списками переменных аргументов, в которых конкретные типы каждого аргумента неизвестны - только его размер в байтах. Это означает, что если я хочу получить int-параметр, я (где-то раньше) определяю: будет параметр с sizeof (int), который обрабатывается с помощью функции обратного вызова xyz.

Моя переменная функция аргумента теперь должна собирать всю информацию из своего вызова, реальные операции, специфичные для типа данных (которые также могут быть пользовательскими типами данных), обрабатываются только через callback-функции.

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

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

int fn( int anz, ... )
{
        char*   p;
        int             i;
        int             j;
        va_list args;
        char*   val;
        char*   valp;
        int             size = sizeof( double ); 

        va_start( args, anz );

        val = malloc( size );

        for( i = 0; i < anz; i++ )
        {
                memcpy( val, args, size );
                args += size;

                printf( "%lf\n", *( (double*)val ) );
        }

        va_end( args );
}

int main()
{
        fn( 1, (double)234.2 );
        fn( 3, (double)1234.567, (double)8910.111213, (double)1415.161718 );

        return 0;
}

У меня работает, под Linux (gcc). Но мой вопрос сейчас: это действительно портативный? Или он выйдет из строя под другими системами и компиляторами?

Мой альтернативный подход состоял в том, чтобы заменить

                memcpy( val, args, size );
                args += size;

с

            for( j = 0; j < size; j++ )
                    val[j] = va_arg( args, char );

но тогда мои ценности пошли не так.

Есть идеи или помощь по этому поводу?

Ответы [ 6 ]

1 голос
/ 09 июля 2010

Ненаучный тест.

  • AIX 5.3 с GCC 4.2 - работает
  • HP-UX 11.23 с aCC 5.56 - не
  • Linux (SUSE10.2) с GCC 4.1 - не
  • Solaris 10 с CC 5.9 - не

Все Linux, Solaris и HP-UX жаловались на строку args += size;.

В противном случае, совершенно очевидно, что va_arg() был включен по причине.Например, в стеке SPARC используется совершенно по-другому.

1 голос
/ 09 июля 2010

Выполнение арифметики на va_list находится на крайнем конце непереносимого. Вы должны использовать va_arg обычно с типом того же размера, что и аргумент, и он , вероятно, будет работать где угодно. Для того, чтобы быть «ближе к переносимым», вы должны использовать целочисленные типы без знака для этой цели (uint32_t и т. Д.).

1 голос
/ 09 июля 2010

Это не портативный, извините.Формат va_list зависит от компилятора / платформы.

Вы должны использовать va_arg () для доступа к va_list, и вы должны передать правильный тип аргумента в va_list.

Однако я считаю,возможно, что если вы передадите тип правильного размера в va_arg, это сработает.то есть.тип обычно не актуален, только его размер.Тем не менее, даже это не гарантирует работу во всех системах.

Я думаю, что я бы посоветовал взглянуть на ваш дизайн и посмотреть, если вы можете найти альтернативный дизайн - есть ли более подробная информация о том, почему вы пытаетесь сделатьэто что вы можете поделиться?Можете ли вы вместо этого передать va_list обратным вызовам?

Обновление

Причина, по которой побайтовый подход не работает, вероятно, довольно сложна.Что касается стандарта C, причина, по которой он не работает, заключается в том, что он недопустим - вы можете использовать va_arg только для доступа к идентичным типам, которые были переданы в функцию.

Но я подозреваю, что выхотелось бы знать, что происходит за кулисами:)

Первая причина заключается в том, что когда вы читаете, передаете функцию "char" в функцию, она фактически автоматически преобразуется в int, поэтому сохраняется в va_arg каквнутр.Поэтому, когда вы читаете символ, вы читаете память типа int, а не символ, то есть вы фактически не читаете байт за раз.

Еще одна причина состоит в том, чточтобы сделать выравнивание - на некоторых архитектурах (одним примером были бы совсем недавние процессоры ARM), «двойник» должен быть выровнен по 64-битной (или иногда даже 128-битной) границе.То есть для значения указателя p p% 16 (модуль p 16, в байтах, т. Е. 128 бит) должен равняться 0. Таким образом, когда они упакованы в va_arg, компилятор, вероятно, будет гарантировать, что любые двойные значения имеют пространство(заполнение) добавлено, чтобы они возникали только при правильном выравнивании - но вы не учитываете это, когда читаете записи байт за раз.

(Могут быть и другие причины - яне очень хорошо знаком с внутренними работами va_arg.)

0 голосов
/ 10 июля 2010

Для C99, , если Я могу предположить, что все ваши аргументы являются целочисленными типами, но могут быть просто разной ширины или подписи, которые вы можете избежать с помощью макроса.Таким образом, вы даже можете преобразовать свой список аргументов в массив без боли для пользователя, который вызывает это.

#define myFunc(...) myRealFunc(NARGS(__VA_ARGS__), (uintmax_t const[]){ __VA_ARGS__})

, где

void myRealFunc(size_t len, uintmax_t const param*);

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

Чтобы объяснить немного, что делает макрос:

  • он помещаетчисло аргументов для первого параметра myRealFunc
  • создает временный массив ( составной литерал ) правильного размера и инициализирует его аргументами (все приведено к uintmax_t)
  • , если NARGS выполнено правильно, ваш список аргументов оценивается только во время предварительной обработки, как токены, а не во время выполнения.Таким образом, ваши параметры не оцениваются во время выполнения более одного раза

Теперь ваши функции обратного вызова будут вызываться myRealFunc с использованием любой магии, которую вы хотели бы разместить там.Поскольку при вызове им потребуется параметр другого целочисленного типа, параметр uintmax_t param[i] будет приведен обратно к этому типу.

0 голосов
/ 09 июля 2010

Могу ли я предложить заменить переменные аргументы массивом пустых указателей?

0 голосов
/ 09 июля 2010

В этом случае избегание va_args, вероятно, будет чище, потому что вы все равно в конечном итоге будете привязаны к определенному количеству аргументов в коде в точке вызова.Я бы пошел с передачей массивов аргументов.

  struct arg
  {
     void* vptr;
     size_t len;
  };

  void fn( struct arg* args, int nargs );

В этом отношении, я бы также перенес определение данных, либо int, как упомянуто ранее в комментариях, либо указатель на структуру, если этоболее сложное определение данных.

...