Como o C offsetof macro funciona? [duplicado]

9
  

Possível duplicar:
Por que esse C trabalho de código?
Como você usa offsetof ( ) em uma estrutura?

Eu li sobre esse deslocamento da macro na Internet, mas isso não explica o que é usado.

#define offsetof(a,b) ((int)(&(((a*)(0))->b)))

O que está tentando fazer e qual é a vantagem de usá-lo?

    
por Samuel Liew 26.10.2011 в 03:47
fonte

4 respostas

11

Ele não tem vantagens e não deve ser usado, pois invoca um comportamento indefinido (e usa o tipo errado - int em vez de size_t ).

O padrão C define uma macro offsetof em stddef.h que realmente funciona, para casos em que você precisa do deslocamento de um elemento em uma estrutura, como:

#include <stddef.h>

struct foo {
    int a;
    int b;
    char *c;
};

struct struct_desc {
    const char *name;
    int type;
    size_t off;
};

static const struct struct_desc foo_desc[] = {
    { "a", INT, offsetof(struct foo, a) },
    { "b", INT, offsetof(struct foo, b) },
    { "c", CHARPTR, offsetof(struct foo, c) },
};

que permitiria que você preenche programaticamente os campos de struct foo pelo nome, por exemplo ao ler um arquivo JSON.

    
por R.. 26.10.2011 / 03:55
fonte
26

R .. está correto em sua resposta para a segunda parte da sua pergunta: este código não é recomendado quando se usa um compilador C moderno.

Mas, para responder à primeira parte da sua pergunta, o que isso está realmente fazendo é:

(
  (int)(         // 4.
    &( (         // 3.
      (a*)(0)    // 1.
     )->b )      // 2.
  )
)

Trabalhando de dentro para fora, isso é ...

  1. Transmitindo o valor zero para o tipo de ponteiro de estrutura a*
  2. Obtendo o campo struct b deste objeto struct (colocado ilegalmente)
  3. Obtendo o endereço deste campo b
  4. Transmitindo o endereço para um int

Conceitualmente, isso está colocando um objeto struct no endereço de memória zero e, em seguida, localizando o endereço de um determinado campo. Isso pode permitir que você descubra os deslocamentos na memória de cada campo em uma estrutura para que você possa escrever seus próprios serializadores e desserializadores para converter estruturas de e para matrizes de bytes.

É claro que se você realmente desreferencia um ponteiro zero, o programa falharia, mas na verdade tudo acontece no compilador e nenhum ponteiro zero real é desreferenciado no tempo de execução.

Na maioria dos sistemas originais que o C rodava no tamanho de um int era de 32 bits e era o mesmo que um ponteiro, então isso realmente funcionou.

    
por Eamonn O'Brien-Strain 26.10.2011 / 04:15
fonte
4

Está localizando o deslocamento de byte de um membro específico de um struct . Por exemplo, se você tivesse a seguinte estrutura:

struct MyStruct
{
    double d;
    int i;
    void *p;
};

Então você teria offsetOf(MyStruct, d) == 0 , offsetOf(MyStruct, i) == 8 e offsetOf(MyStruct, p) == 12 (ou seja, o membro chamado d é 0 bytes do início da estrutura, etc.).

A maneira como ele funciona é que ele finge que uma instância de sua estrutura existe no endereço 0 (a ((a*)(0)) part) e, em seguida, pega o endereço do membro da estrutura pretendida e o converte em um inteiro. Embora a desreferência de um objeto no endereço 0 seja normalmente um erro, não há problema em pegar o endereço porque o operador de endereço & e o membro cancelaram -> cancelam um ao outro.

É normalmente usado para estruturas de serialização generalizadas. Se você tiver um código para converter entre algum tipo de dados (por exemplo, bytes em um arquivo ou da rede) e estruturas de dados em memória, é conveniente criar um mapeamento de nome de membro para deslocamento de membro, para que você possa serializar ou desserialize os valores de uma maneira genérica.

    
por Adam Rosenfield 26.10.2011 / 03:58
fonte
-2

A implementação do offset da macro é realmente irrelevante.

O padrão C real define-o como em 7.17.3:

offsetof(type, member-designator)

que se expande para uma expressão constante de número inteiro que possui o tipo size_t, cujo valor é o deslocamento em bytes, para o membro da estrutura (designado por member-designator), desde o início de sua estrutura (designada por tipo). O tipo e designador de membro deve ser tal que dado static type t; .

Confie na resposta de Adam Rosenfield.

O R está completamente errado, e ele tem muitos usos - especialmente sendo capaz de dizer quando o código não é portátil entre as plataformas.

(OK, é C ++, mas nós o usamos em asserções de tempo de compilação de modelo estático para garantir que nossas estruturas de dados não alterem o tamanho entre as plataformas / versões.)

    
por Adrian Cornish 26.10.2011 / 04:26
fonte