Как я могу получить / установить член структуры по смещению - PullRequest
27 голосов
/ 11 января 2010

Игнорирование проблем дополнения / выравнивания и учитывая следующую структуру, каков наилучший способ получить и установить значение 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)
  • приведение структуры к типу указателя из одного слова (char?)
  • создание указателя int и установка адреса (*charpointer + offset?)
  • useмой указатель int для установки содержимого памяти

, но я немного запутался в приведении к типу char, или если что-то вроде memset более подходит или если я вообще полностью его заправляюнеправильно.

Ура за любую помощь

Ответы [ 6 ]

32 голосов
/ 11 января 2010

Подход, который вы обрисовали в общих чертах, примерно верен, хотя вы должны использовать 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;
}
26 голосов
/ 11 января 2010

Полная техника:

  1. Получить смещение с помощью offsetof:

    b_offset = offsetof (struct mystruct, member_b);

  2. Получите адрес вашей структуры в виде указателя на символ *.

    char * sc = (char *) s;

  3. Добавьте add смещение к адресу структуры, приведите значение к указателю на соответствующий тип и разыменование:

    * (int *) (sc + b_offset)

4 голосов
/ 11 января 2010

Игнорирование отступов и выравнивания, как вы сказали ...

Если элементы, на которые вы указываете, целиком относятся к одному типу, как в вашем примере, вы можете просто привести структуру к нужному типу и обработать ее как массив:

printf("%i", ((int *)(&s))[1]);
3 голосов
/ 27 декабря 2014

Возможно рассчитать смещение на основе структуры и 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;
}
1 голос
/ 11 января 2010

В этом конкретном примере вы можете обратиться к нему по *((int *) ((char *) s + sizeof(int))). Я не уверен, почему вы этого хотите, поэтому я предполагаю дидактические цели, поэтому объяснение следует.

Бит кода переводится как: взять память, начинающуюся с адреса s, и обращаться с ней как с памятью, указывающей на char. К этому адресу добавьте sizeof(int) char -chunks - вы получите новый адрес. Возьмите значение, которое адрес создал таким образом, и обработайте его как int.

Обратите внимание, что запись *(s + sizeof(int)) даст адрес в s плюс sizeof(int) размер (mystruct) кусков

Редактировать: согласно комментарию Андрея, используя offsetof :

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

Редактировать 2: я заменил все byte с char с, так как sizeof(char) гарантированно будет равным 1.

0 голосов
/ 12 января 2010

Из ваших комментариев звучит то, что вы действительно делаете, упаковываете и распаковываете кучу разнородных типов данных в один блок памяти. В то время как вы можете делать это с помощью прямого приведения указателя, как и предлагали большинство других ответов:

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;
}

(аналогично для других типов).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...