Estrutura preenchimento e embalagem

209

Considerar:

struct mystruct_A
{
   char a;
   int b;
   char c;
} x;

struct mystruct_B
{
   int b;
   char a;
} y;

Os tamanhos das estruturas são 12 e 8, respectivamente.

Essas estruturas são acolchoadas ou embaladas?

Quando ocorre o preenchimento ou a embalagem?

Manu
fonte
24
A arte perdida de C Embalagem Estrutura - catb.org/esr/structure-packing
Paolo
paddingtorna as coisas maiores. packingtorna as coisas menores. Totalmente diferente.
smwikipedia

Respostas:

264

O preenchimento alinha os membros da estrutura aos limites de endereço "naturais" - digamos, os intmembros teriam compensações, que estão mod(4) == 0na plataforma de 32 bits. O preenchimento está ativado por padrão. Ele insere as seguintes "lacunas" em sua primeira estrutura:

struct mystruct_A {
    char a;
    char gap_0[3]; /* inserted by compiler: for alignment of b */
    int b;
    char c;
    char gap_1[3]; /* -"-: for alignment of the whole struct in an array */
} x;

A embalagem , por outro lado, impede que o compilador faça preenchimento - isso deve ser explicitamente solicitado - no GCC, é __attribute__((__packed__))o seguinte:

struct __attribute__((__packed__)) mystruct_A {
    char a;
    int b;
    char c;
};

produziria estrutura de tamanho 6 em uma arquitetura de 32 bits.

Uma observação: o acesso à memória desalinhada é mais lento em arquiteturas que permitem (como x86 e amd64) e é explicitamente proibido em arquiteturas de alinhamento estrito como SPARC.

Nikolai Fetissov
fonte
2
Eu me pergunto: a proibição de memória desalinhada na faísca significa que ela não pode lidar com uma matriz de bytes usual? O empacotamento de estruturas, como eu sei, é usado principalmente na transmissão (ou seja, em rede) de dados, quando você precisa converter uma matriz de bytes em uma estrutura e certifique-se de que uma matriz se encaixe nos campos de uma estrutura. Se a faísca não pode fazer isso, como estão aqueles que trabalham ?!
Hi-Angel
14
É exatamente por isso que, se você observar os layouts de cabeçalho IP, UDP e TCP, verá que todos os campos inteiros estão alinhados.
Nikolai Fetissov
17
O "Lost Art of C Embalagem Estrutura", explica o preenchimento e embalagem ptimisations - catb.org/esr/structure-packing
Rob11311
3
O primeiro membro tem que vir primeiro? Eu pensei que a organização depende totalmente da implementação e não pode ser invocada (mesmo de versão para versão).
Allyourcode
4
+ allyourcode O padrão garante que a ordem dos membros seja preservada e que o primeiro membro comece no deslocamento 0.
8789 martinsnev
64

( As respostas acima explicaram o motivo com bastante clareza, mas não parecem totalmente claras sobre o tamanho do preenchimento, portanto, adicionarei uma resposta de acordo com o que aprendi com The Lost Art of Structure Packing , que evoluiu para não se limitar a C, mas também aplicável a Go, Rust. )


Alinhamento de memória (para struct)

Regras:

  • Antes de cada membro individual, haverá preenchimento para que ele comece em um endereço divisível por seu tamanho.
    por exemplo, no sistema de 64 bits, intdeve começar no endereço divisível por 4 e longpor 8, shortpor 2.
  • chare char[]são especiais, podem ser qualquer endereço de memória; portanto, eles não precisam de preenchimento antes deles.
  • Pois struct, além da necessidade de alinhamento de cada membro individual, o tamanho da estrutura inteira será alinhado com um tamanho divisível pelo tamanho do maior membro individual, preenchendo o final.
    por exemplo, se o maior membro do struct for longdivisível por 8, intdepois por 4 e shortdepois por 2.

Ordem do membro:

  • A ordem do membro pode afetar o tamanho real da estrutura, portanto, lembre-se disso. por exemplo, stu_ce stu_ddo exemplo abaixo têm os mesmos membros, mas em ordem diferente e resultam em tamanho diferente para as 2 estruturas.

Endereço na memória (para struct)

Regras:

  • Sistema de 64 bits O
    endereço estrutural começa a partir de (n * 16)bytes. ( Você pode ver no exemplo abaixo, todos os endereços hexadecimais impressos das estruturas terminam com 0. )
    Motivo : o maior membro individual possível da estrutura é de 16 bytes ( long double).
  • (Atualização) Se uma estrutura contiver apenas umcharmembro, seu endereço poderá começar em qualquer endereço.

Espaço vazio :

  • O espaço vazio entre 2 estruturas poderia ser usado por variáveis ​​não-estruturais que poderiam caber. Por
    exemplo, test_struct_address()abaixo, a variável xreside entre estruturas ge adjacentes h.
    Não importa se xé declarado, ho endereço do endereço não será alterado, xapenas reutilize o espaço vazio que foi gdesperdiçado.
    Caso semelhante para y.

Exemplo

( para sistema de 64 bits )

memory_align.c :

/**
 * Memory align & padding - for struct.
 * compile: gcc memory_align.c
 * execute: ./a.out
 */ 
#include <stdio.h>

// size is 8, 4 + 1, then round to multiple of 4 (int's size),
struct stu_a {
    int i;
    char c;
};

// size is 16, 8 + 1, then round to multiple of 8 (long's size),
struct stu_b {
    long l;
    char c;
};

// size is 24, l need padding by 4 before it, then round to multiple of 8 (long's size),
struct stu_c {
    int i;
    long l;
    char c;
};

// size is 16, 8 + 4 + 1, then round to multiple of 8 (long's size),
struct stu_d {
    long l;
    int i;
    char c;
};

// size is 16, 8 + 4 + 1, then round to multiple of 8 (double's size),
struct stu_e {
    double d;
    int i;
    char c;
};

// size is 24, d need align to 8, then round to multiple of 8 (double's size),
struct stu_f {
    int i;
    double d;
    char c;
};

// size is 4,
struct stu_g {
    int i;
};

// size is 8,
struct stu_h {
    long l;
};

// test - padding within a single struct,
int test_struct_padding() {
    printf("%s: %ld\n", "stu_a", sizeof(struct stu_a));
    printf("%s: %ld\n", "stu_b", sizeof(struct stu_b));
    printf("%s: %ld\n", "stu_c", sizeof(struct stu_c));
    printf("%s: %ld\n", "stu_d", sizeof(struct stu_d));
    printf("%s: %ld\n", "stu_e", sizeof(struct stu_e));
    printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));

    printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));
    printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));

    return 0;
}

// test - address of struct,
int test_struct_address() {
    printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));
    printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));
    printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));

    struct stu_g g;
    struct stu_h h;
    struct stu_f f1;
    struct stu_f f2;
    int x = 1;
    long y = 1;

    printf("address of %s: %p\n", "g", &g);
    printf("address of %s: %p\n", "h", &h);
    printf("address of %s: %p\n", "f1", &f1);
    printf("address of %s: %p\n", "f2", &f2);
    printf("address of %s: %p\n", "x", &x);
    printf("address of %s: %p\n", "y", &y);

    // g is only 4 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "g", "h", (long)(&h) - (long)(&g));

    // h is only 8 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "h", "f1", (long)(&f1) - (long)(&h));

    // f1 is only 24 bytes itself, but distance to next struct is 32 bytes(on 64 bit system) or 24 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "f1", "f2", (long)(&f2) - (long)(&f1));

    // x is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between g & h,
    printf("space between %s and %s: %ld\n", "x", "f2", (long)(&x) - (long)(&f2));
    printf("space between %s and %s: %ld\n", "g", "x", (long)(&x) - (long)(&g));

    // y is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between h & f1,
    printf("space between %s and %s: %ld\n", "x", "y", (long)(&y) - (long)(&x));
    printf("space between %s and %s: %ld\n", "h", "y", (long)(&y) - (long)(&h));

    return 0;
}

int main(int argc, char * argv[]) {
    test_struct_padding();
    // test_struct_address();

    return 0;
}

Resultado da execução - test_struct_padding():

stu_a: 8
stu_b: 16
stu_c: 24
stu_d: 16
stu_e: 16
stu_f: 24
stu_g: 4
stu_h: 8

Resultado da execução - test_struct_address():

stu_g: 4
stu_h: 8
stu_f: 24
address of g: 0x7fffd63a95d0  // struct variable - address dividable by 16,
address of h: 0x7fffd63a95e0  // struct variable - address dividable by 16,
address of f1: 0x7fffd63a95f0 // struct variable - address dividable by 16,
address of f2: 0x7fffd63a9610 // struct variable - address dividable by 16,
address of x: 0x7fffd63a95dc  // non-struct variable - resides within the empty space between struct variable g & h.
address of y: 0x7fffd63a95e8  // non-struct variable - resides within the empty space between struct variable h & f1.
space between g and h: 16
space between h and f1: 16
space between f1 and f2: 32
space between x and f2: -52
space between g and x: 12
space between x and y: 12
space between h and y: 8

Assim, o início do endereço para cada variável é g: d0 x: dc h: e0 y: e8

insira a descrição da imagem aqui

Eric Wang
fonte
4
"Regras" na verdade deixou muito claro, não consegui encontrar regras diretas em nenhum lugar. Obrigado.
Pervez Alam
2
@PervezAlam O livro <The Lost Art of C Structure Packing>explica muito bem as regras, mesmo que seja um pouco mais longo do que esta resposta. O livro está disponível gratuitamente on-line: catb.org/esr/structure-packing
Eric Wang
Vou tentar, btw é limitado a estrutura de embalagem? Apenas curiosidades, como eu gostei da explicação no livro.
Pervez Alam
1
@PervezAlam É um livro muito curto, com foco principalmente na tecnologia que reduziria a pegada de memória do programa c, leva apenas no máximo vários dias para concluir a leitura.
Eric Wang
1
@ValidusOculus Sim, significa 16 bytes alinhados.
Eric Wang
44

Sei que essa pergunta é antiga e a maioria das respostas aqui explica muito bem o preenchimento, mas, ao tentar entendê-lo, achei que ter uma imagem "visual" do que está acontecendo ajudou.

O processador lê a memória em "pedaços" de tamanho definido (palavra). Digamos que a palavra do processador tenha 8 bytes. Ele olhará para a memória como uma grande linha de blocos de construção de 8 bytes. Sempre que precisar obter algumas informações da memória, atingirá um desses blocos e as obterá.

Alinhamento de variáveis

Como parece na imagem acima, não importa onde esteja um caractere (1 byte de comprimento), pois ele estará dentro de um desses blocos, exigindo que a CPU processe apenas 1 palavra.

Quando lidamos com dados maiores que um byte, como 4 bytes int ou 8 bytes duplos, a maneira como eles estão alinhados na memória faz a diferença em quantas palavras terão que ser processadas pela CPU. Se os blocos de 4 bytes estiverem alinhados de uma maneira, eles sempre se encaixam no interior de um bloco (o endereço de memória é múltiplo de 4) apenas uma palavra terá que ser processada. Caso contrário, um pedaço de 4 bytes pode ter parte de si mesmo em um bloco e parte de outro, exigindo que o processador processe 2 palavras para ler esses dados.

O mesmo se aplica a um duplo de 8 bytes, mas agora ele deve estar em um endereço de memória múltiplo de 8 para garantir que ele esteja sempre dentro de um bloco.

Isso considera um processador de texto de 8 bytes, mas o conceito se aplica a outros tamanhos de palavras.

O preenchimento funciona preenchendo as lacunas entre esses dados para garantir que eles estejam alinhados com esses blocos, melhorando assim o desempenho durante a leitura da memória.

No entanto, como indicado em outras respostas, às vezes o espaço importa mais do que o desempenho propriamente dito. Talvez você esteja processando muitos dados em um computador que não possui muita RAM (o espaço de troca pode ser usado, mas é MUITO mais lento). Você pode organizar as variáveis ​​no programa até que o menor preenchimento seja feito (como foi bastante exemplificado em algumas outras respostas), mas se isso não for suficiente, você poderá desabilitar explicitamente o preenchimento, que é o que é o empacotamento .

IanC
fonte
3
Isso não explica o empacotamento da estrutura, mas ilustra muito bem o alinhamento de palavras da CPU.
David Foerster
Você desenhou isso em tinta? :-)
Ciro Santilli escreveu:
1
@ CiroSantilli709大抓捕六四事件法轮功, foi no GIMP, mas eu acho que eu teria salvo algum tempo fazendo isso de pintura embora haha
CNI
1
Ainda melhor desde o código-fonte aberto (Y)
Ciro Santilli
21

O empacotamento da estrutura suprime o preenchimento da estrutura, o preenchimento usado quando o alinhamento é mais importante, o empacotamento usado quando o espaço é mais importante.

Alguns compiladores fornecem #pragmapara suprimir o preenchimento ou torná-lo compactado em n número de bytes. Alguns fornecem palavras-chave para fazer isso. Geralmente, o pragma usado para modificar o preenchimento da estrutura estará no formato abaixo (depende do compilador):

#pragma pack(n)

Por exemplo, o ARM fornece a __packedpalavra-chave para suprimir o preenchimento da estrutura. Consulte o manual do compilador para saber mais sobre isso.

Portanto, uma estrutura compactada é uma estrutura sem preenchimento.

Estruturas geralmente compactadas serão usadas

  • economizar espaço

  • formatar uma estrutura de dados para transmitir pela rede usando algum protocolo (essa não é uma boa prática, é claro, porque você precisa
    lidar com endianness)

user2083050
fonte
5

Preenchimento e embalagem são apenas dois aspectos da mesma coisa:

  • embalagem ou alinhamento é o tamanho para o qual cada membro é arredondado
  • padding é o espaço extra adicionado para corresponder ao alinhamento

Na mystruct_Asuposição de um alinhamento padrão de 4, cada membro é alinhado em um múltiplo de 4 bytes. Como o tamanho de charé 1, o preenchimento para ae cé de 4 a 1 = 3 bytes, enquanto não é necessário preenchimento para o int bqual já existem 4 bytes. Funciona da mesma maneira para mystruct_B.

Casablanca
fonte
1

O empacotamento da estrutura é feito somente quando você instrui explicitamente o compilador para compactar a estrutura. Preenchimento é o que você está vendo. Seu sistema de 32 bits está preenchendo cada campo para alinhar as palavras. Se você tivesse dito ao seu compilador para compactar as estruturas, elas teriam 6 e 5 bytes, respectivamente. Não faça isso embora. Não é portátil e faz com que os compiladores gerem código muito mais lento (e às vezes até com bugs).

nmichaels
fonte
1

Não há mas sobre isso! Quem quiser entender o assunto deve fazer o seguinte,

snr
fonte
1

Regras para preenchimento:

  1. Todo membro da estrutura deve estar em um endereço divisível por seu tamanho. O preenchimento é inserido entre os elementos ou no final da estrutura para garantir que essa regra seja atendida. Isso é feito para acesso de barramento mais fácil e eficiente pelo hardware.
  2. O preenchimento no final da estrutura é decidido com base no tamanho do maior membro da estrutura.

Por que regra 2: considere a seguinte estrutura,

Estrutura 1

Se criarmos uma matriz (de 2 estruturas) dessa estrutura, nenhum preenchimento será necessário no final:

Matriz Struct1

Portanto, tamanho de struct = 8 bytes

Suponha que devemos criar outra estrutura como abaixo:

Struct 2

Se criarmos uma matriz dessa estrutura, existem 2 possibilidades, do número de bytes de preenchimento necessário no final.

A. Se adicionarmos 3 bytes no final e o alinharmos por int e não por muito tempo:

Matriz Struct2 alinhada com int

B. Se adicionarmos 7 bytes no final e alinhá-lo por muito tempo:

Matriz Struct2 alinhada a Long

O endereço inicial da segunda matriz é um múltiplo de 8 (ou seja, 24). O tamanho da estrutura = 24 bytes

Portanto, alinhando o endereço inicial da próxima matriz da estrutura a um múltiplo do maior membro (ou seja, se criarmos uma matriz dessa estrutura, o primeiro endereço da segunda matriz deve começar em um endereço que seja múltiplo do maior membro da estrutura.Aqui está 24 (3 * 8)), podemos calcular o número de bytes de preenchimento necessários no final.

AlphaGoku
fonte
-1

O alinhamento da estrutura de dados é a maneira como os dados são organizados e acessados ​​na memória do computador. Consiste em duas questões separadas, mas relacionadas: alinhamento de dados e preenchimento da estrutura de dados . Quando um computador moderno lê ou grava em um endereço de memória, ele faz isso em blocos do tamanho de palavras (por exemplo, blocos de 4 bytes em um sistema de 32 bits) ou maior. Alinhamento de dados significa colocar os dados em um endereço de memória igual a alguns múltiplos do tamanho da palavra, o que aumenta o desempenho do sistema devido à maneira como a CPU lida com a memória. Para alinhar os dados, pode ser necessário inserir alguns bytes sem sentido entre o final da última estrutura de dados e o início da próxima, que é o preenchimento da estrutura de dados.

  1. Para alinhar os dados na memória, um ou mais bytes (endereços) vazios são inseridos (ou deixados em branco) entre endereços de memória que são alocados para outros membros da estrutura durante a alocação de memória. Esse conceito é chamado de preenchimento de estrutura.
  2. A arquitetura de um processador de computador é capaz de ler 1 palavra (4 bytes no processador de 32 bits) da memória por vez.
  3. Para aproveitar essa vantagem do processador, os dados são sempre alinhados como um pacote de 4 bytes, o que leva a inserir endereços vazios entre os endereços de outros membros.
  4. Devido a esse conceito de preenchimento de estrutura em C, o tamanho da estrutura nem sempre é igual ao que pensamos.
manoj yadav
fonte
1
Por que você precisa vincular o mesmo artigo 5 vezes na sua resposta? Por favor, mantenha apenas um link para o exemplo. Além disso, como você está vinculando ao seu artigo, é necessário divulgar esse fato.
precisa