web-dev-qa-db-ja.com

オフセットによって構造体メンバーを取得/設定するにはどうすればよいですか

パディング/アライメントの問題を無視するで、次の構造体が与えられた場合、メンバー名を使用せずにmember_bの値を取得および設定するための最良の方法は何ですか。

struct mystruct {
    int member_a;
    int member_b;
}
struct mystruct *s = malloc(sizeof(struct mystruct));

別の言い方をすると、ポインター/オフセットの観点から、次のことをどのように表現しますか?

s->member_b = 3;
printf("%i",s->member_b);

私の推測は

  • member_a(int)のサイズを見つけてオフセットを計算します
  • 構造体を1つのWordポインタ型(char?)にキャストします
  • intポインターを作成し、アドレスを(*charpointer + offset?に)設定します
  • intポインタを使用してメモリの内容を設定する

しかし、char型へのキャストについて少し混乱したり、memsetのようなものがより適切である場合や、一般的にこれを完全に間違っている場合は、.

助けてくれて乾杯

28
Chris Farmiloe

概説したアプローチはおおよそ正しいですが、自分でオフセットを計算する代わりにoffsetofを使用する必要があります。なぜあなたがmemsetについて言及しているのかはわかりません-ブロックの内容を指定された値に設定します。

これがどのように機能するかを示すためのコードです:

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

typedef struct x {
    int member_a;
    int member_b;
} x;

int main() { 
    x *s = malloc(sizeof(x));
    char *base;
    size_t offset;
    int *b;

    // initialize both members to known values
    s->member_a = 1;
    s->member_b = 2;

    // get base address
    base = (char *)s;

    // and the offset to member_b
    offset = offsetof(x, member_b);

    // Compute address of member_b
    b = (int *)(base+offset);

    // write to member_b via our pointer
    *b = 10;

    // print out via name, to show it was changed to new value.
    printf("%d\n", s->member_b);
    return 0;
}
32
Jerry Coffin

完全なテクニック:

  1. Offsetofを使用してオフセットを取得します。

    b_offset = offsetof(struct mystruct、member_b);

  2. 構造体のアドレスをchar *ポインターとして取得します。

    char * sc =(char *)s;

  3. オフセットを構造体アドレスに追加し、値を適切なタイプへのポインタにキャストして逆参照します。

    *(int *)(sc + b_offset)

26

あなたが言ったように、パディングと配置を無視します...

例のように、ポイントしている要素が完全に単一の型である場合は、構造体を目的の型にキャストして配列として扱うことができます。

printf("%i", ((int *)(&s))[1]);
4
mskfisher

構造体と参照ポインタとしてのNULLに基づいてオフセットを計算できます。例: "&(((type *)0)-> field)"

例:

struct my_struct {
    int x;
    int y;
    int *m;
    int *h;
};
int main()
{
    printf("offset %d\n", (int) &((((struct my_struct*)0)->h)));
    return 0;
}
3
Obik

この特定の例では、*((int *) ((char *) s + sizeof(int)))でアドレス指定できます。なぜあなたがそれを望んでいるのかわからないので、私は教訓的な目的を想定しているので、説明は次のとおりです。

コードのビットは次のように変換されます。アドレスsから始まるメモリを取得し、charを指すメモリとして扱います。そのアドレスにsizeof(int)char- chunksを追加します-新しいアドレスを取得します。このようにして作成されたアドレスの値を取り、intとして扱います。

*(s + sizeof(int))を書き込むと、sにアドレスを加え、sizeof(int) sizeof(mystruct)チャンクが得られることに注意してください。

編集:アンドレイのコメントに従って offsetof を使用:

*((int *) ((byte *) s + offsetof(struct mystruct, member_b)))

編集2:sizeof(char)が1であることが保証されているため、すべてのbytesをcharsに置き換えました。

1
laura

コメントから、実際に行っているのは、さまざまなデータ型の束を単一のメモリブロックにパックおよびアンパックすることです。他のほとんどの答えが示唆しているように、あなたはcan直接ポインタキャストでそれをやめることを避けます:

void set_int(void *block, size_t offset, int val)
{
    char *p = block;

    *(int *)(p + offset) = val;
}

int get_int(void *block, size_t offset)
{
    char *p = block;

    return *(int *)(p + offset);
}

問題は、これが移植不可能であることです。タイプが正しい配置でブロック内に格納されるようにする一般的な方法はありません。一部のアーキテクチャでは、配置されていないアドレスに対してロードまたは格納を実行できません。ブロックのレイアウトが宣言された構造によって定義される特殊なケースでは、構造体のレイアウトに正しい配置を確保するために必要なパディングが含まれるため、問題ありません。ただし、名前でメンバーにアクセスすることはできないため、実際にはそうではないようです。

これを移植可能にするには、memcpyを使用する必要があります。

void set_int(void *block, size_t offset, int val)
{
    char *p = block;

    memcpy(p + offset, &val, sizeof val);
}

int get_int(void *block, size_t offset)
{
    char *p = block;
    int val;

    memcpy(&val, p + offset, sizeof val);
    return val;
}

(他のタイプも同様)。

0
caf