Странное поведение #pragma pack - PullRequest
0 голосов
/ 11 июля 2019

При использовании #pragma pack.

я столкнулся со странной проблемой.

У меня есть следующие структуры:

  1. st_a: упаковано с #pragma pack(1). Размер = 1 байт. Содержит битовые поля.
  2. st_b: упаковано с #pragma pack(2). Размер = 14 байт.
  3. st_c: упаковано с #pragma pack(2). Размер = 16 байт. Содержит st_a и st_b
  4. st_d: упаковано с #pragma pack(2). Размер = 16 байт. Содержит st_a и содержимое st_b (st_b)

Итак, поскольку st_a имеет 1 байт, упакованный в #pragma pack(1), и поскольку он находится внутри st_c, который упакован в #pragma pack(2), должен быть один дополнительный байт заполнения в st_c сразу после st_a и за этим дополнительным байтом должно следовать содержимое st_b, которое является символьным буфером четной длины (10).

Но эта штука работает странно, когда я вынимаю содержимое st_b и помещаю его прямо в st_a. Заполнение отображается после символьного буфера, а не до (см. Вывод ниже).

Может кто-нибудь объяснить, почему происходит это странное поведение?

Код:

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;

#pragma pack(push)
#pragma pack(1)
typedef struct A {
    int a : 1;
    int b : 1;
    int c : 1;
    int d : 1;
    int e : 1;
    int f : 1;
    int g : 1;
    int h : 1;
} st_a;
#pragma pack(pop)

#pragma pack(push)
#pragma pack(2)
typedef struct B {
    unsigned char buf[10];
    int x;
} st_b;

typedef struct C {
    st_a temp1;
    st_b temp2;
} st_c;

typedef struct D {
    st_a temp3;
    unsigned char buf1[10];
    int x1;
} st_d;
#pragma pack(pop)

void print_hex(unsigned char* packet) {
    for (int i = 0; i < 16; i++) {
        printf("%x ", packet[i]);
    } printf("\n");
}

int main() {
    st_c one;
    one.temp1.a = 0;
    one.temp1.b = 0;
    one.temp1.c = 1;
    one.temp1.d = 0;
    one.temp1.e = 0;
    one.temp1.f = 0;
    one.temp1.g = 0;
    one.temp1.h = 0;
    memcpy(&one.temp2.buf, "ABCDEFGHIJ", 10);
    one.temp2.x = 10;

    st_d two;
    two.temp3.a = 0;
    two.temp3.b = 0;
    two.temp3.c = 1;
    two.temp3.d = 0;
    two.temp3.e = 0;
    two.temp3.f = 0;
    two.temp3.g = 0;
    two.temp3.h = 0;
    memcpy(&two.buf1, "ABCDEFGHIJ", 10);
    two.x1 = 10;

    print_hex((unsigned char*) &one);
    print_hex((unsigned char*) &two);
    cout << sizeof(st_c) << " " << sizeof(st_a) << " " << sizeof(st_b) << " " << sizeof(st_d) << endl;

    return 0;
}

Выход:

4 5b 41 42 43 44 45 46 47 48 49 4a a 0 0 0
4 41 42 43 44 45 46 47 48 49 4a 0 a 0 0 0
16 1 14 16

Примечание. Я использую GCC версии 4.4.x.

Несколько замечаний по поводу вывода :

В первой строке 5b - это байт заполнения, введенный между 4, который равен 1 байту st_a и 41, который является первым символом буфера st_b.

Во второй строке 0 - это байт заполнения, введенный между 4a, который является последним символом буфера, и a, который является целым числом после символьного буфера в st_d.

Третья строка печатает размер всех структур.

1 Ответ

0 голосов
/ 11 июля 2019

Вы неправильно определяете заполнение.Вы ожидаете, что заполнение будет нулевым байтом?Нет причин ожидать этого.Значение в байтах заполнения может быть любым.Нули, которые вы видите в конце, являются частью 4 байтов st_b.x1.Это значение равно 10, которое в качестве целого числа дополнения к младшему двоичному порядку 2 будет сохранено как 0A 00 00 00

04 5b 41 42 43 44 45 46 47 48 49 4a 0a 00 00 00
^  ^  ^-----------------------------^---------^
|  |  st_b.buf                      st_b.x1  
|  random padding byte
|
st_a

. Лучший способ взглянуть на заполнение - использовать макрос offsetof.Например:

#include <stdio.h>
#include <stddef.h>

#pragma pack(push)
#pragma pack(1)
struct Pack1 {
  char x;
};

#pragma pack(2)
struct Pack2 {
  short y;
};

struct Combined {
  struct Pack1 p1;
  struct Pack2 p2;
};
#pragma(pop)

int main()
{
  printf("offset of p1: %u, offset of p2: %u\n",
         offsetof(struct Combined, p1),
         offsetof(struct Combined, p2));
 return 0;
}

Это выводит:

offset of p1: 0, offset of p2: 2

, который ясно показывает, что между p1 и p2.

имеется один байт заполнения. Другой способвидеть заполнение как нули - значит обнулять все байты структуры сразу после ее объявления:

st_c one;
memcpy(&one, 0, sizeof(st_c);

Тогда результат вашей программы будет выглядеть так:

04 00 41 42 43 44 45 46 47 48 49 4a 0a 00 00 00
...