используя объединения в параметрах функции - PullRequest
5 голосов
/ 03 августа 2009

Я недавно обнаружил, что что-то компилируется (хотя я не уверен, что это разрешено). Моя потребность в этом заключается в следующем: мой проект выводит машинный код для выбранной арки (которая может быть, а может и не быть той же самой, что и та, которая выполняет программу). Итак, я бы хотел поддерживать до 64-битных архитектур прямо сейчас (при этом поддерживая существующие 32- и 16-битные арки). Мое текущее решение состоит в том, чтобы 'base' new_state был просто uint64_t, и вручную приводил к 16 и 32-битным при необходимости. , Хотя я обнаружил, что вы можете компилировать объединения в параметре функции. Итак, функция, как это компилируется:

int pcg_new_state(pcg_state *s,int arch,void *mem,int sz,
             union{
    uint16_t b16;
    uint32_t b32;
    uint64_t b64;
}base ,int self_running);

Является ли такая вещь вообще "законной", хотя или поддерживается другими компиляторами? Кроме того, я не могу понять, как вызвать эту функцию, не создавая объединение и не передавая это объединение в new_state.

Ответы [ 5 ]

7 голосов
/ 03 августа 2009

Подводя итог: Да, это допустимо в C, хотя и недопустимо в C ++. Последний содержит эту записку, которая объясняет разницу

Изменение: В C ++ типы не могут быть определены в типах возвращаемых значений или параметров. В C эти определения типов допускаются

Пример:

void f( struct S { int a; } arg ) {} // valid C, invalid C++
enum E { A, B, C } f() {} // valid C, invalid C++
  • Обоснование: При сравнении типов в разных единицах компиляции C ++ полагается на эквивалентность имен, когда C полагается на структурную эквивалентность. Что касается типов параметров: поскольку тип, определенный в списке параметров, будет находиться в области действия функции, единственные допустимые вызовы в C ++ будут изнутри самой функции.
  • Влияние на исходную функцию: Удаление семантически четко определенной функции.
  • Сложность конвертации: Семантическая трансформация. Определения типов должны быть перемещены в область файла или в заголовочные файлы.
  • Насколько широко используются: Редко. Этот стиль определений типов считается плохим стилем кодирования.

Структурная эквивалентность в C определяется понятием «совместимость типов». Это позволяет C обрабатывать многие типы, как если бы они были идентичными, даже если они теоретически различны - потому что они объявлены в двух разных единицах перевода. В C ++ эта концепция не существует, потому что типы имеют связь и сопоставляются с одной и той же сущностью (т. Е. Чтобы функции-члены могли связываться друг с другом).

Обратите внимание, что приведенное выше объяснение основано на C89, который не учитывал имя тега структуры при определении совместимости типов. В C89 черновик соответствующий текст гласит следующее:

Кроме того, два типа структуры, объединения или перечисления, объявленные в отдельных единицах перевода, являются совместимыми, если они имеют одинаковое количество членов, одинаковые имена членов и совместимые типы элементов; для двух структур члены должны быть в одинаковом порядке;

В C99 проверка типов более строга: если одна структура имеет имя тега, другая декларация структуры должна иметь то же имя тега. Таким образом, в вашем случае безымянного типа объединения, чтобы объявить функцию в другом TU с совместимым типом, вам потребуется снова безымянное объединение, если вы хотите иметь действительный код C99 (без неопределенного поведения) - вы не можете «обмануть» и используйте именованный союз в одном TU и безымянный союз в другом TU. Мне кажется, что этот "трюк" действителен для C89, хотя. C99 TC3 6.2.7/1

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


То, как вы хотите это сделать, не работает. Вызов функции преобразует аргументы в тип параметров, как при обычном присваивании.

Итак, чтобы это работало, у вас должен быть аргумент, совместимый с типом параметра. Для двух объединений, объявленных в одной и той же единице перевода , это означает, что их тип должен совпадать - это единственный способ, которым вы можете создать совместимый тип в одной и той же единице перевода. Но это не может работать, потому что объявление безымянного объединения создает уникальный новый тип - никакой возможности «сослаться» на него с помощью другого объявления.

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

union base_type {
        uint16_t b16;
        uint32_t b32;
        uint64_t b64;
};

int pcg_new_state(pcg_state *s,int arch,void *mem,int sz,
                  union base_type base,int self_running);

union base_type base_b16(uint16_t t) 
{ union base_type b; b.b16 = t; return b; }
union base_type base_b32(uint32_t t) 
{ union base_type b; b.b32 = t; return b; }
union base_type base_b64(uint64_t t) 
{ union base_type b; b.b64 = t; return b; }

Теперь это может выглядеть следующим образом

pcg_new_state(...., base_b32(4211), ....);
1 голос
/ 03 августа 2009

Я быстро просмотрел стандарт (Edit: C ++) и не увидел ничего, что бы запрещало его ни в 8.3.5 - Функции [dcl.fct], ни в 9.5 - Объединения [class.union]. Как полуобразованное предположение, я бы сказал, что это законно - принять профсоюз, но не объявлять одну строку в таком виде. GCC дает:

ошибка: типы не могут быть определены в типах параметров.

Так что вам нужно определить тип заранее.

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

0 голосов
/ 03 августа 2009

Размер союза в любом случае будет размером с самого большого члена - так что вы ничего не получите. Вы также можете просто указать параметр uint64_t, поскольку преобразования из больших в меньшие типы без знака хорошо определены и обычно дешевы в реализации. (Например, присвоение uint64_t для uint16_t можно сделать, просто взяв 16 младших битов более широкого типа).

РЕДАКТИРОВАТЬ : Например,

int pcg_new_state(pcg_state *s,int arch,void *mem,int sz, uint64_t param_base ,int self_running)
{
    /* Implementation for 16 bit arch */
    uint16_t base = param_base;

    /* ... more code ... */
}
0 голосов
/ 03 августа 2009

Вы можете получить это для компиляции в GCC, предоставив объединению имя в функции, но вы не сможете использовать его из-за объема определения, как предупреждает GCC:

test_union.c:14: warning: ‘union X’ declared inside parameter list
test_union.c:14: warning: its scope is only this definition or declaration, which is probably not what you want

Код:

int pcg_new_state(pcg_state *s,int arch,void *mem,int sz,
                         union X{
        uint16_t b16;
        uint32_t b32;
        uint64_t b64;
}base ,int self_running);
0 голосов
/ 03 августа 2009

Как насчет этого:

typedef struct _base
{
    union
    {
        uint16_t b16;
        uint32_t b32;
        uint64_t b64;
    };
}Base;

int func(Base b)
{
    return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{

    Base b;
    b.b16 = 0xFFFF;
    func(b);

    return 0;

}
...