efeito do pacote pragma


233

Fiquei me perguntando se alguém poderia me explicar o que a #pragma packdeclaração do pré - processador faz e, mais importante, por que alguém iria querer usá-la.

Verifiquei a página do MSDN , que ofereceu algumas informações, mas esperava ouvir mais de pessoas com experiência. Eu já vi isso em código antes, embora pareço não encontrar mais onde.


1
Força um alinhamento / empacotamento específico de uma estrutura, mas, como todas as #pragmadiretivas, elas são definidas pela implementação.
dreamlax

A mod s = 0onde A é o endereço es é o tamanho do tipo de dados; isso verifica se os dados não estão desalinhados.
legends2k

Respostas:


422

#pragma packinstrui o compilador a compactar membros da estrutura com um alinhamento específico. A maioria dos compiladores, quando você declara uma estrutura, inserirá preenchimento entre os membros para garantir que eles estejam alinhados aos endereços apropriados na memória (geralmente um múltiplo do tamanho do tipo). Isso evita a penalidade de desempenho (ou erro definitivo) em algumas arquiteturas associadas ao acesso a variáveis ​​que não estão alinhadas corretamente. Por exemplo, dados inteiros de 4 bytes e a seguinte estrutura:

struct Test
{
   char AA;
   int BB;
   char CC;
};

O compilador pode escolher colocar a estrutura na memória da seguinte maneira:

|   1   |   2   |   3   |   4   |  

| AA(1) | pad.................. |
| BB(1) | BB(2) | BB(3) | BB(4) | 
| CC(1) | pad.................. |

e sizeof(Test)seria 4 × 3 = 12, mesmo que contenha apenas 6 bytes de dados. O caso de uso mais comum para #pragma(que eu saiba) é ao trabalhar com dispositivos de hardware nos quais você precisa garantir que o compilador não insira preenchimento nos dados e que cada membro siga o anterior. Com #pragma pack(1), a estrutura acima ficaria assim:

|   1   |

| AA(1) |
| BB(1) |
| BB(2) |
| BB(3) |
| BB(4) |
| CC(1) |

E sizeof(Test)seria 1 × 6 = 6.

Com #pragma pack(2), a estrutura acima ficaria assim:

|   1   |   2   | 

| AA(1) | pad.. |
| BB(1) | BB(2) |
| BB(3) | BB(4) |
| CC(1) | pad.. |

E sizeof(Test)seria 2 × 4 = 8.

A ordem das variáveis ​​em struct também é importante. Com variáveis ​​ordenadas da seguinte forma:

struct Test
{
   char AA;
   char CC;
   int BB;
};

e com #pragma pack(2), a estrutura seria apresentada assim:

|   1   |   2   | 

| AA(1) | CC(1) |
| BB(1) | BB(2) |
| BB(3) | BB(4) |

e sizeOf(Test)seria 3 × 2 = 6.


76
Pode valer a pena adicionar as desvantagens da embalagem. (acessos de objetos não alinhados são lentos no melhor caso, mas irá causar erros em algumas plataformas.)
jalf

11
Parece que os alinhamentos "penalidade de desempenho" mencionados podem realmente ser um benefício em alguns sistemas danluu.com/3c-conflict .

6
@ Pacerier Na verdade não. Esse post fala sobre um alinhamento bastante extremo (alinhamento nos limites de 4KB). A CPU espera certos alinhamentos mínimos para vários tipos de dados, mas esses requerem, no pior caso, alinhamento de 8 bytes (sem contar os tipos de vetores que podem exigir alinhamento de 16 ou 32 bytes). Não alinhar com esses limites geralmente gera um impacto perceptível no desempenho (porque uma carga pode ter que ser feita como duas operações em vez de uma), mas o tipo está bem alinhado ou não. O alinhamento mais rigoroso do que isso não
ajuda

6
Em outras palavras, um duplo espera estar em um limite de 8 bytes. Colocá-lo em um limite de 7 bytes prejudicará o desempenho. Mas colocá-lo em um limite de 16, 32, 64 ou 4096 bytes não comprará nada acima do limite de 8 bytes que você já forneceu. Você obterá o mesmo desempenho da CPU, enquanto obtém uma utilização muito pior do cache pelos motivos descritos nessa postagem.
jalf

4
Portanto, a lição não é "empacotar é benéfico" (empacotar viola o alinhamento natural dos tipos, prejudicando o desempenho), mas simplesmente "não se alinhar além do necessário"
jalf 14/15/15

27

#pragmaé usado para enviar mensagens não portáteis (como neste compilador apenas) para o compilador. Coisas como desativar certos avisos e estruturas de embalagem são razões comuns. Desabilitar avisos específicos é particularmente útil se você compilar com os avisos quando o sinalizador de erros estiver ativado.

#pragma packespecificamente é usado para indicar que a estrutura que está sendo compactada não deve ter seus membros alinhados. É útil quando você tem uma interface mapeada na memória para um pedaço de hardware e precisa controlar exatamente para onde os diferentes membros da estrutura apontam. Notavelmente, não é uma boa otimização de velocidade, pois a maioria das máquinas é muito mais rápida ao lidar com dados alinhados.


17
Para desfazer depois faça o seguinte: #pragma pack (push, 1) e #pragma pack (pop)
malhal

16

Diz ao compilador o limite para alinhar os objetos em uma estrutura. Por exemplo, se eu tiver algo como:

struct foo { 
    char a;
    int b;
};

Em uma máquina típica de 32 bits, você normalmente "deseja" ter 3 bytes de preenchimento entre ae bpara baterrissar em um limite de 4 bytes para maximizar sua velocidade de acesso (e é o que normalmente acontece por padrão).

Se, no entanto, você precisar corresponder a uma estrutura definida externamente, deseje garantir que o compilador estabeleça sua estrutura exatamente de acordo com essa definição externa. Nesse caso, você pode dar ao compilador um aviso #pragma pack(1)para não inserir nenhum preenchimento entre membros - se a definição da estrutura incluir preenchimento entre membros, você o insere explicitamente (por exemplo, geralmente com membros nomeados unusedNou ignoreN, ou algo sobre isso ordem).


"você normalmente" deseja "ter 3 bytes de preenchimento entre aeb para que b aterre em um limite de 4 bytes para maximizar sua velocidade de acesso" - como ter 3 bytes de preenchimento maximizaria a velocidade de acesso?
Ashwin

8
@ Ashwin: Colocar bem um limite de 4 bytes significa que o processador pode carregá-lo emitindo uma única carga de 4 bytes. Embora dependa um pouco do processador, se estiver em um limite estranho, é bem provável que, para carregá-lo, seja necessário que o processador emita duas instruções de carregamento separadas, use um shifter para juntar essas peças. A penalidade típica é da ordem de 3x a carga mais lenta desse item.
Jerry Coffin

... se você olhar o código de montagem para ler int alinhado e não alinhado, a leitura alinhada geralmente é um mnemônico único. A leitura desalinhada pode ter 10 linhas de montagem facilmente, pois reúne o int em conjunto, escolhendo byte por byte e colocando nos locais corretos do registro.
SF.

2
@SF .: Pode ser - mas mesmo quando não é, não se deixe enganar - em uma CPU x86 (por um exemplo óbvio) as operações são realizadas no hardware, mas você ainda obtém aproximadamente o mesmo conjunto de operações e desaceleração.
Jerry Coffin

8

Os elementos de dados (por exemplo, membros de classes e estruturas) normalmente estão alinhados nos limites WORD ou DWORD dos processadores de geração atual, a fim de melhorar os tempos de acesso. A recuperação de um DWORD em um endereço que não seja divisível por 4 requer pelo menos um ciclo de CPU extra em um processador de 32 bits. Portanto, se você tiver, por exemplo, três membros de caracteres char a, b, c;, eles tendem a ocupar 6 ou 12 bytes de armazenamento.

#pragmapermite substituir isso para obter um uso mais eficiente do espaço, à custa da velocidade de acesso ou para obter consistência dos dados armazenados entre diferentes destinos do compilador. Eu me diverti muito com essa transição de código de 16 para 32 bits; Espero que a transferência para código de 64 bits cause os mesmos tipos de dores de cabeça para algum código.


Na verdade, char a,b,c;normalmente são necessários 3 ou 4 bytes de armazenamento (pelo menos em x86) - isso ocorre porque o requisito de alinhamento é de 1 byte. Se não fosse, então como você lidaria char str[] = "foo";? O acesso a charé sempre uma máscara de busca e troca simples, enquanto o acesso a uma intpode ser uma busca ou uma busca, ou apenas uma busca, dependendo de estar alinhado ou não. intpossui (em x86) um alinhamento de 32 bits (4 bytes) porque, caso contrário, você obteria (digamos) meio intem um DWORDe meio no outro, e isso levaria duas pesquisas.
Tim Čas 18/07/12

3

O compilador pode alinhar membros em estruturas para alcançar o desempenho máximo em determinada plataforma. #pragma packA diretiva permite controlar esse alinhamento. Normalmente, você deve deixá-lo por padrão para obter o melhor desempenho. Se você precisar passar uma estrutura para a máquina remota, geralmente utilizará #pragma pack 1para excluir qualquer alinhamento indesejado.


2

Um compilador pode colocar membros da estrutura em limites de bytes específicos por razões de desempenho em uma arquitetura específica. Isso pode deixar um preenchimento não utilizado entre os membros. O empacotamento da estrutura força os membros a serem contíguos.

Isso pode ser importante, por exemplo, se você precisar que uma estrutura esteja em conformidade com um arquivo ou formato de comunicação específico em que os dados que você precisa estejam em posições específicas dentro de uma sequência. No entanto, esse uso não lida com problemas de endianismo, portanto, embora usado, ele pode não ser portátil.

Também pode sobrepor exatamente a estrutura de registro interno de algum dispositivo de E / S, como um controlador UART ou USB, por exemplo, para que o acesso ao registro seja feito através de uma estrutura e não de endereços diretos.


1

Você provavelmente só desejaria usá-lo se estivesse codificando para algum hardware (por exemplo, um dispositivo mapeado para memória) que tivesse requisitos estritos para o pedido e o alinhamento de registros.

No entanto, isso parece uma ferramenta bastante direta para atingir esse objetivo. Uma abordagem melhor seria codificar um mini-driver no assembler e fornecer uma interface de chamada C em vez de se atrapalhar com esse pragma.


Na verdade, eu o uso bastante para economizar espaço em tabelas grandes que não são acessadas com frequência. Lá, é apenas para economizar espaço e não para um alinhamento estrito. (Just votou-lo, btw Alguém havia lhe dado um voto negativo..)
Todd Lehman

1

Eu o usei no código antes, embora apenas para interagir com o código legado. Este era um aplicativo Mac OS X Cocoa que precisava carregar arquivos de preferência de uma versão anterior do Carbon (que era compatível com a versão original do M68k System 6.5 ... você entendeu). Os arquivos de preferência na versão original eram um despejo binário de uma estrutura de configuração, que costumava #pragma pack(1)evitar ocupar espaço extra e economizar lixo (ou seja, os bytes de preenchimento que estariam na estrutura).

Os autores originais do código também usavam #pragma pack(1)para armazenar estruturas usadas como mensagens na comunicação entre processos. Acho que o motivo aqui foi evitar a possibilidade de tamanhos de preenchimento desconhecidos ou alterados, pois o código às vezes examinava uma parte específica da estrutura da mensagem contando vários bytes desde o início (ewww).


1

Vi pessoas usá-lo para garantir que uma estrutura ocupe toda uma linha de cache para impedir o compartilhamento falso em um contexto multithread. Se você tiver um grande número de objetos que serão compactados livremente por padrão, poderá economizar memória e melhorar o desempenho do cache para compactá-los com mais firmeza, embora o acesso à memória desalinhada normalmente diminua a velocidade das coisas, por isso pode haver uma desvantagem.


0

Observe que existem outras maneiras de obter consistência de dados que o #pragma pack oferece (por exemplo, algumas pessoas usam o #pragma pack (1) para estruturas que devem ser enviadas pela rede). Por exemplo, consulte o seguinte código e sua saída subsequente:

#include <stdio.h>

struct a {
    char one;
    char two[2];
    char eight[8];
    char four[4];
};

struct b { 
    char one;
    short two;
    long int eight;
    int four;
};

int main(int argc, char** argv) {
    struct a twoa[2] = {}; 
    struct b twob[2] = {}; 
    printf("sizeof(struct a): %i, sizeof(struct b): %i\n", sizeof(struct a), sizeof(struct b));
    printf("sizeof(twoa): %i, sizeof(twob): %i\n", sizeof(twoa), sizeof(twob));
}

A saída é a seguinte: sizeof (struct a): 15, sizeof (struct b): 24 sizeof (twoa): 30, sizeof (twob): 48

Observe como o tamanho da estrutura a é exatamente o que a contagem de bytes é, mas a estrutura b possui um preenchimento adicionado (consulte isso para obter detalhes sobre o preenchimento). Ao fazer isso, em oposição ao pacote #pragma, você pode controlar a conversão do "formato de ligação" nos tipos apropriados. Por exemplo, "char two [2]" em um "short int" etc.


Não, está errado. Se você observar a posição na memória de b.two, não será um byte após b.one (o compilador pode (e geralmente alinha) b.two para que fique alinhado ao acesso à palavra). Para a.two, é exatamente um byte depois de a.one. Se você precisar acessar a.two como um int curto, você deve ter 2 alternativas, usar uma união (mas isso geralmente falha se houver um problema de endianidade) ou descompactar / converter por código (usando a função ntohX apropriada)
xryl669

1
sizeofretorna um size_tque deve ser impresso usando%zu . Usando o especificador formato errado invoca indefinido comportamento
phuclv

0

Por que alguém quer usá-lo?

Para reduzir a memória da estrutura

Por que não se deve usá-lo?

  1. Isso pode levar a uma penalidade no desempenho, porque alguns sistemas funcionam melhor com dados alinhados
  2. Alguma máquina falhará ao ler dados não alinhados
  3. O código não é portátil
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.