Передача 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
Тогда опубликуйте свой фактический код цели 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 может ответить на вопрос, связанный с концепцией C, даже если код написан на Objective-C.   -  person Amin Negm-Awad    schedule 09.08.2015


Ответы (3)


С «структурой в трюке с объединением» вам нужно позаботиться, потому что члены структуры могут быть дополнены:

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

ИСО/МЭК 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;

… если реализации это нравится.

Так что оверлей в союзе не получится, даже это маловероятно.

Это было бы похоже с битовыми полями:

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

Это связано с тем, что компилятор должен поместить следующее битовое поле в ту же единицу измерения, если оно подходит, но может выбрать единицу измерения.

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

там же, 6.7.2.1 – 10

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

Как упоминалось в комментарии, в битовых полях нет ничего плохого.

person Amin Negm-Awad    schedule 09.08.2015
comment
Битовые поля определяются реализацией; компилятор имеет большой выбор в макете. Я ожидаю, что первый MultiPortStruct не будет иметь заполнения, но вы можете добавить статическое утверждение, чтобы проверить это. - 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.

ХТН

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