Предаване на 32-битова структура в 32-битов аргумент на целочислена функция

Имам някакъв чист C код в моя Objective-C проект. В един от API-тата, които използвам, мога да се регистрирам за обратно извикване с функция, която приема 32-битов целочислен параметър:

void Callback(Packet* packet, int32_t port);

Искам да мога да изпратя моето обратно повикване два 16-битови порта вместо един 32-битов порт. Естествено, бих могъл просто да използвам побитови операции, но бих предпочел нещо по-ясно.

Ето текущото ми решение с обединение:

typedef struct {
    int16_t port1;
    int16_t port2;
} MultiPortStruct;

typedef union {
    int32_t port;
    MultiPortStruct portStruct;
} MultiPortAdaptor;

В подателя:

void registerCallback(int16_t port1, int16_t port2) {
    MultiPortAdaptor adaptor;
    adaptor.portStruct.port1 = port1;
    adaptor.portStruct.port2 = port2;
    int32_t port = adaptor.port

    RegisterAPICallback(&myCallback, port);
}

В обратното извикване:

void myCallback(Packet* packet, int32_t port) {
    MultiPortAdaptor adaptor;
    adaptor.port = port;
    int16_t port1 = adaptor.portStruct.port1;
    int16_t port2 = adaptor.portStruct.port2;

    // do stuff
}

Правилен ли е този подход или има проблеми с него? (Например: трябва ли да нулирам обединенията на адаптерите? Добре ли е да имам достъп до различни членове на едно и също обединение?) Има ли по-прост начин?


АКТУАЛИЗАЦИЯ:

Добре, убеден съм: дори това да е възможно, вероятно не е добра идея. Реших просто да използвам набор от функции, за да извършвам побитовите операции вместо мен:

int32_t SubPortsToPort(int16_t port1, int16_t port2)
int16_t PortToSubPort1(int32_t port)
int16_t PortToSubPort2(int32_t port)

Сега всичко, което трябва да направя, е следното:

void registerCallback(int16_t port1, int16_t port2) {
    int32_t port = SubPortsToPort(port1, port2);

    RegisterAPICallback(&myCallback, port);
}

И в обратното извикване:

void myCallback(Packet* packet, int32_t port) {
    int16_t port1 = PortToSubPort1(port);
    int16_t port2 = PortToSubPort2(port);

    // do stuff
}

По-малко код, по-малко грижи!


person Archagon    schedule 09.08.2015    source източник
comment
Моля, изберете езиков етикет. Псевдонимът на съюза не е разрешен в C++, но е разрешен в C.   -  person M.M    schedule 09.08.2015
comment
Вие сте около милиард пъти по-добре, ако просто използвате побитова операция. Можете да го скриете зад вградена функция, ако това ви кара да се чувствате по-добре.   -  person M.M    schedule 09.08.2015
comment
Технически използвам Objective-C, така че не съм сигурен кое е по-подходящо.   -  person Archagon    schedule 09.08.2015
comment
Това отново е различен език, използвайте маркера objective-c   -  person M.M    schedule 09.08.2015
comment
Да, но мисля, че хората ще имат проблем с Objective-C въпрос без действителен Objective-C код, нали?   -  person Archagon    schedule 09.08.2015
comment
Публикувайте действителния си Objective C код тогава?   -  person M.M    schedule 09.08.2015
comment
Е, точно това е нещото. Използвам C в моята програма Objective-C. (Objective-C е в по-голямата си част надмножество на C.) Така че мисля, че етикетът c е най-подходящ тук.   -  person Archagon    schedule 09.08.2015
comment
Предлагам да използвате тагове c и objective-c и премахване на c++. C етикетът обикновено се използва за код, компилиран от C компилатор и отговарящ на ISO C спецификацията. Хората, които идват тук и виждат етикета C, трябва да са наясно, че може да има нещо друго   -  person M.M    schedule 09.08.2015
comment
recipient->(packet, adaptor.port); е незаконно в C   -  person M.M    schedule 09.08.2015
comment
@Archagon Мисля, че все пак трябва да използвате маркера Objective-C, тъй като може да има разлики в компилатора, които няма да бъдат взети под внимание от програмист, който не е Objective-c.   -  person cdonts    schedule 09.08.2015
comment
@MattMcNabb Objective-C отговаря на стандарта C с изключение на очевидния факт, че ключовите думи на Objective-C не са разрешени като идентификатори. Всеки C разработчик може да отговори на Q, свързано с C концепция, дори кодът е написан на Objective-C.   -  person Amin Negm-Awad    schedule 09.08.2015


Отговори (3)


С "трика за структура в съюз" трябва да внимавате, защото членовете на структурата могат да бъдат подплатени:

Може да има неименувана подложка в структурен обект, но не и в началото му.

ISO/IEC 9899:TC3, 6.7.2.1 – 13

Следователно, …

typedef struct {
  int16_t port1;
  int16_t port2;
} MultiPortStruct;

... може да има оформление като ...

typedef struct {
  int32_t port1;
  int32_t port2;
} MultiPortStruct;

… ако изпълнението харесва това.

Така че наслагването в съюза няма да работи, дори това не е много вероятно.

Би било подобно с bitfields:

typedef struct {
  int port1:16;
  int port2:16;
} MultiPortStruct;

Това е така, защото компилаторът трябва да постави следното битово поле в същата единица, ако се побира, но може да избере единицата.

Една реализация може да разпредели всяка адресируема единица за съхранение, достатъчно голяма, за да побере битово поле. Ако остане достатъчно място, битово поле, което непосредствено следва друго битово поле в структура, трябва да бъде пакетирано в съседни битове на същата единица.

ibidem, 6.7.2.1 – 10

Следователно, теоретично е възможно имплементацията да избере int16 като единица за разпределение и да постави подложка между полетата, дори това няма смисъл и е много малко вероятно.

Както бе споменато в коментара, няма нищо лошо в bitfields.

person Amin Negm-Awad    schedule 09.08.2015
comment
Битовите полета са дефинирани от изпълнението; компилаторът има голям избор в оформлението. Очаквам първият MultiPortStruct да няма подложка, но можете да добавите статичен assert, за да потвърдите това. - person M.M; 09.08.2015
comment
1. Вече казах, че biltfields има поведение, определено от изпълнението. 2. Едно твърдение би тествало проблема, но не го решава. - person Amin Negm-Awad; 09.08.2015

Първо е добре да съхранявате в един член и да четете от друг член на същия union при условие, че четете само байтове, които сте записали - всички останали имат неуточнени стойности.

Проблемът с това, което правите, е възможното въвеждане от компилатора на подложка между полетата, което би унищожило вашето внимателно подредено припокриване. С настоящите компилатори и архитектури на процесора вероятно ще се оправите, но няма нужда да го оставяте на случайността, тъй като има няколко начина за коригиране на подравняването на полетата.

Най-простият, макар и зависим от компилатора, метод е да използвате атрибута packed:

typedef struct __attribute((packed))
{
   int16_t port1;
   int16_t port2;
} MultiPortStruct;

Това просто инструктира компилатора да не добавя подпълване, така че горното винаги ще бъде 32-битово дори при архитектури, които използват, да речем, 4-байтово подравняване за 2-байтови стойности.

Този атрибут се поддържа от Clang, GCC & LLVM (може би не всички версии?) - компилаторът ще ви каже, ако не е така. Ако използвате компилатор, който не поддържа атрибута (или еквивалентен), тогава можете да разгледате използването на функциите на езика C _Alignof и _Alignas.

HTH

person CRD    schedule 09.08.2015

Както казаха други, struct може да има подложка (макар и малко вероятно). Друг подход, който поне няма да има вътрешна подложка, би бил

typedef struct {
    int16_t port[2];
} MultiPortStruct;

масивите никога нямат вътрешни подложки и първото поле на struct винаги е в началото. Сега теоретично това все още може да има подложка в края, така че трябва да проверите с нещо подобно

_Static_assert(sizeof(MultiPortStruct)==2*sizeof(int16_t));
person Jens Gustedt    schedule 09.08.2015
comment
Това е най-здравата идея +1. Но все още не решава целочислено оформление (endianess). Наистина мисля, че битовите операции са най-добрият начин за решаване на това. - person Amin Negm-Awad; 09.08.2015
comment
Защо тогава просто не поставите масива в съюза? Избягването на структурата напълно избягва и подложката. - person Nikolai Ruhe; 14.08.2015