Как в памяти размещаются вложенные структуры с разным выравниванием?

Я разработчик C#, пишу клиент для сервера, написанного на C++. Сервер передает какие-то произвольные данные по TCP/IP клиенту, и мы должны собрать их на другом конце. Сервер отправляет нам сначала описание данных, затем сами данные.

Проблемная структура:

struct Inner_S
{
  double a;
  double b[4][4];
};

#pragma pack(1)
struct Packed_S
{
  uint8_t c;
  Inner_S d;
};

Сервер сообщает клиенту, что внешняя структура имеет выравнивание 1, а внутренняя структура имеет выравнивание 8. Спецификация протокола говорит:

Выравнивание полей в потоковой структуре выполняется в соответствии со спецификацией 64-разрядного двоичного интерфейса приложений C++ Itanium (т. е. так же, как типичный компилятор GNU на типичной 64-разрядной платформе).

Я нашел спецификацию двоичного интерфейса 64-разрядного приложения Itanium C++. Я думаю, что часть, которую я ищу, находится в разделе «Распределение членов, отличных от виртуальных баз», но я там теряюсь.

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

В настоящее время я обрабатываю структуру таким образом, что, по мнению моих пользователей, это неправильно:

(начать структуру с выравнивания 1)(заполнение не требуется)(читать простое значение)c(начинать внутреннюю структуру с выравнивания 8)(добавить дополнение к выравниванию 8)0000000(прочитать поле)aaaaaaaa(начало массива)(прочитать простое значение)bbbbbbbb. ....

Этот метод поддерживается не менее одного сайта.

Итак, когда я анализирую эти данные, как мне обрабатывать выравнивание в Inner_S?

caaaaaaaabbbbbbbbb.... (я думаю?) caaaaaaaaa0000000bbbbbbbb.... (выглядит неправильно)


person Denise Skidmore    schedule 29.10.2014    source источник
comment
Вы знаете, простая программа на C++, которая выводит sizeof ваши типы и offsetof каждое из ваших полей, поможет вам отлаживать подобные вещи намного быстрее! :-)   -  person Cameron    schedule 30.10.2014
comment
Это отличная идея.   -  person Denise Skidmore    schedule 30.10.2014
comment
Одна проблема с определением этого экспериментальным путем заключается в том, что то, как это делается, зависит от компилятора, а не от языка. Я думаю, что если я использую GCC на разных платформах, он будет работать одинаково, но я не уверен?   -  person Denise Skidmore    schedule 30.10.2014
comment
Конкретная проблема, на которую жаловался клиент, была устранена путем выравнивания с начала структуры, а не с какой-либо глобальной ссылки, и добавления отступов в конце структуры вместо того, чтобы позволить контейнеру сразу возобновить управление выравниванием. Это по-прежнему вряд ли можно назвать кратким и полным описанием того, что должно произойти, и в результате схема памяти сводит на нет точку внутренней структуры, состоящую из восьми блоков.   -  person Denise Skidmore    schedule 31.10.2014


Ответы (1)


Как предложил @Cameron, я попробовал это с offsetof, так как это связано с типами POD.

#include <iostream>
#include <cstddef>
#include <cstdint>
using namespace std;

struct Inner_S
{
  double a;
  double b[4][4];
};

#pragma pack(1)
struct Packed_S
{
  uint8_t c;
  Inner_S d;
};

int main() {
    cout << "Size: " << sizeof(Packed_S) << endl;
    cout << "c offset: " << offsetof(Packed_S, c) << endl;
    cout << "d offset: " << offsetof(Packed_S, d) << endl;
    cout << "a offset: " << offsetof(Packed_S, d.a) << endl;
    cout << "b offset: " << offsetof(Packed_S, d.b) << endl;
    return 0;
}

Выход

Size: 137
c offset: 0
d offset: 1
a offset: 1
b offset: 9

Таким образом, используя вашу запись, структура упакована как caaaaaaaabbbbbbbb..... Обратите внимание, что если вы удалите директиву #pragma pack(1), компилятор добавит 3 байта заполнения после c.

см. здесь

person sjdowling    schedule 29.10.2014
comment
Вы компилируете 32 бит? Я бы ожидал 7 байтов заполнения без пакета #pragma (1) при 64-битной компиляции. - person Denise Skidmore; 30.10.2014
comment
@DeniseSkidmore Это дает мне точно такой же результат с 64-битным компилятором. - person sjdowling; 30.10.2014
comment
Интересно. Существуют ли настройки компилятора для упаковки по умолчанию, которые отличаются в наших средах? - person Denise Skidmore; 30.10.2014
comment
@DeniseSkidmore: сомневаюсь, pack(1) говорит компилятору не добавлять отступы - person Mooing Duck; 31.10.2014
comment
Но Inner_S не находится под директивой pack(1). В противном случае не было бы заполнения вообще, а не 3 против 7. - person Denise Skidmore; 31.10.2014
comment
Я получаю 7 байтов заполнения, используя 64-битную компиляцию g++ для -m64. Я думаю, что @sjdowling использует исполняемый файл 64-битного компилятора, но компилирует для 32 бит (эквивалент -m32 для g++). - person Mark Lakata; 05.11.2014
comment
@MarkLakata Я определенно создавал 64-битные исполняемые файлы. Моя цель была x86_64-w64-mingw32, а компилятор gcc версии 4.7.3. - person sjdowling; 05.11.2014
comment
g++ junk.cpp -o junk -std=c++11 -x86_64-w64-mingw32 дает мне исполняемый файл с 8-байтовым выравниванием. Вы можете запустить file junk? Мой говорит: junk: ELF 64-bit LSB executable, x86-64,... - person Mark Lakata; 05.11.2014
comment
Именно поэтому я визжал, когда в первой версии спецификации говорилось, что упаковка будет сделана так, как это сделал бы любой стандартный компилятор C++. Насколько я понимаю, он не стандартизирован. Я заставил автора спецификации указать что-то более конкретное, и тогда я получил ту другую спецификацию, на разборку которой у меня не было достаточно времени, чтобы понять. - person Denise Skidmore; 07.11.2014