2014-01-30 2 views
36

Я думаю, что это невозможно в C, но попросите проверить это. Можно ли сделать арифметику между членами структуры без реальной переменной этого типа? Например:C указатель arithmetic без объекта структуры

typedef struct _s1 
{ 
    int a; 
    int b; 
    int c; 
} T1; 

Я хочу видеть смещение члена «c» по сравнению с началом структуры. Это легко, если у меня есть переменная:

T1 str; 

int dist = (int)&str.c - (int)&str; 

Но моя структура слишком велик, и он не имеет ни одного члена в ОЗУ (только в EEPROM). И я хочу сделать некоторые вычисления адресов, но не определять член RAM. Я могу выполнить работу со указателем структуры вместо переменной структуры (она будет стоить всего 4 байта), но случай интересен для меня.

+8

Определить поддельный указатель и посмотреть? – Cthulhu

+3

На это, вероятно, ответили здесь и обсудили в другом месте миллион раз. Я предполагаю, что ответ должен был искать Google. Вы Google это? –

+0

Если ваша структура действительно такая большая, то ее листинг, а адрес одного из ее членов в 'int', скорее всего, вызовет у вас горе (указатели составляют 4/8 байт,' int' должен быть только 2 байты в размере, но большинство реализаций используют 4 байта) –

ответ

56

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

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

12

stddef.h имеет макрос, называемый offsetof, который дает смещение элемента от начала структуры.

size_t t = offsetof(T1 , c) ; 

Таким образом, вам не нужно фактически объявлять какие-либо структурные переменные.

8

В заголовке stddef.h есть макрос; offsetof. Вы можете использовать его на структуры, как это:

int dist = (int)offsetof(T1, c); 
25

Прежде всего, вы производите индивидуальные адреса до int. Я не думаю, что это такая хорошая идея. должно быть не менее int. байт или более размер, адрес/указатель обычно 4 или 8 байтов. Отливка их до int просто не подходит. Если бы я их бросил, я бы использовал unsigned long для 32-битных систем и unsigned long long для 64 бит. Я бы использовал последнее, чтобы быть в безопасности.
Тем не менее, я бы не выбрал адреса вообще и просто использовал макрос offsetof.

Добавить #include <stddef.h> в файл и использовать offsetof макрос, который отбрасывает значение смещения для size_t типа, а не как int, заметьте.Он работает, просто используя 0 в качестве адреса памяти в структуры, а затем возвращает адрес данного элемента, что дает фактическое смещение:

size_t offset = offsetoff(T1, c); 
        //struct, member 

Как Zack отметил в своем комментарии, следующую часть ответа не имеет значения, но для ссылок, которые он содержит, и для полноты я просто оставлю его здесь, поскольку offsetof требуется из всех реализаций:

Определите макрос самостоятельно, если вы его не используете По какой-то причине у меня есть stddef.h (что должно быть, потому что offsetof был частично стандартным в течение некоторого времени: C89 и выше), например так:

#ifndef offsetof 
#define offsetof(s, m) ((size_t)&(((s *) 0)->m)) 
#endif 

Это должно сделать трюк, но будьте осторожны: эта реализация может привести к непредсказуемому поведению. How and why is explained here
Я принял определение offsetof от the stddef.h source I found here, поэтому вы можете просто загрузить этот файл и использовать его вместо того, чтобы определять макрос в своем собственном файле, но имейте в виду, что используемый вами offsetof не на 100% надежный.

Во всяком случае, этого достаточно на данный момент, больше информации на Google, но вот несколько ссылок:

+6

Определение 'offsetof' немного опасно. Реализация, которую вы показываете, является пограничным случаем неопределенного поведения (я могу выкопать ссылку для этого), но это ОК, чтобы сделать это для разработчика, потому что они знают (или должны убедиться), как ведет себя компилятор. – pmr

+1

@pmr: Добавлено несколько ссылок на мой ответ, THX для указания на него. Я не знал о приличиях 'offsetof' (мой основной источник знания C), являющийся K & R, который вообще не упоминает макрос). в любом случае +1 к вашему ответу за указание выравнивания данных на OP –

+2

Нет никакого оправдания для реализации, не обеспечивающей 'stddef.h', с' offsetof' в ней, в настоящее время - это требуется от всех реализаций, автономных или других , поскольку C89. – zwol

16

Вы можете использовать offsetof определены в stddef.h.

Я знаю, что это выходит за рамки того, что вы просили, но есть одно интересное использование offsetof, используемое, например, в коде ядра linux, макросе container_of. container_of может вернуть вам адрес материнской структуры, поскольку указатель на один из ее членов:

#define container_of(ptr, type, member) ({      \ 
     const typeof(((type *)0)->member) *__mptr = (ptr); \ 
     (type *)((char *)__mptr - offsetof(type,member));}) 

Что вы можете использовать, например:

// pointer_to_c points to a member of the struct, c in this case 
// and you want to get the address of the container 
T1 *container; 
container = container_of(pointer_to_c, T1, c); 
+0

Мне нравится макрос 'container_of', но' (char *) 'cast выглядит немного _legacy_ для меня, если вы не против моего высказывания. Я думаю, что в наше время мы использовали бы указатель void –

+2

@EliasVanOotegem. Я думаю, что (char *) необходимо, потому что позже __mptr используется для арифметики указателя. Он должен быть точно уменьшен с помощью offsetof (...) байтов. Вы никогда не должны использовать арифметику для указателей viud http://stackoverflow.com/questions/3523145/pointer-arithmetic-for-void-pointer-in-c – fede1024

+1

Хорошая точка ... Я просто видел '(char *) __ mptr', не переставал рассматривать последствия 'void * - size_t' :) –

Смежные вопросы