Собственно, вот код:
#include <stdio.h>
#define OFFSET_OF(TData, field) ((char)&(((TData)nullptr)->field) - (char*)nullptr)
struct TEST_STRUCT
{
char field0;
int field1;
double field2;
char field3;
};
int main()
{
printf(
"offset of field1: %zu\n"
"offset of field2: %zu\n"
"offset of field3: %zu\n",
OFFSET_OF(TEST_STRUCT, field1),
OFFSET_OF(TEST_STRUCT, field2),
OFFSET_OF(TEST_STRUCT, field3)
);
return 0;
}
Компилируем, запускаем:
offset of field1: 4
offset of field2: 8
offset of field3: 16
Дизассемблируем, смотрим, что сгенерировал компилятор:
.text:0000000140003370 ; =============== S U B R O U T I N E =======================================
.text:0000000140003370
.text:0000000140003370
.text:0000000140003370 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:0000000140003370 public main
.text:0000000140003370 main proc near ; CODE XREF: __tmainCRTStartup+2EB↑p
.text:0000000140003370 ; DATA XREF: .pdata:00000001400072A0↓o
.text:0000000140003370 sub rsp, 28h
.text:0000000140003374 call __main
.text:0000000140003379 mov r9d, 16
.text:000000014000337F mov r8d, 8
.text:0000000140003385 mov edx, 4
.text:000000014000338A lea rcx, aOffsetOfField1 ; "offset of field1: %zu\noffset of field2"...
.text:0000000140003391 call printf
.text:0000000140003396 xor eax, eax
.text:0000000140003398 add rsp, 28h
.text:000000014000339C retn
.text:000000014000339C main endp
.text:000000014000339C
.text:000000014000339C ; ---------------------------------------------------------------------------
Видим, что всё работает как надо. Компилятор, как и ожидалось, предвычислил значения смещений на этапе компиляции. Никакого явного UB не произошло.
Однако в моём предыдущем вопросе один не особо дружелюбный пользователь назвал такой код вакханалией и бредом. Мол, тут разыменуется nullptr, и по-этому так делать нельзя. Однако, хоть разыменование и происходит, но оно ведь тут чисто формальное! Реального обращение к памяти через nullptr тут ведь не происходит! Более того, код данного макроса выполняется ещё на этапе компиляции, и по факту его результат - константы!
Соответственно, у меня вопрос: почему так нельзя делать? Если такая конструкция работает и не приводит к UB, значит ответ прост - так можно делать. Если же такой код небезопасен, то просьба объяснить, чем именно. Приведите, пожалуйста, пример кода, где выполнение конструкции ((char*)&(((TData*)nullptr)->field) - (char*)nullptr) приведёт к реальному UB!
P.S. Я знаю, что существует "стандартный" макрос offsetof, однако мой вопрос не о нём.
&(((TEST_STRUCT*)nullptr)->field)приводит к некоторым проблемам. Даже если игнорировать разыменование нулевого указателя, есть ещё одна проблема: вычисление разности двух указателей, которые не указывают на элементы одного массива (или гипотетический элемент за последним), и не являются оба нулевыми, что также вызывает UB. – wololo Dec 30 '23 at 18:58std::ptrdiff_t, но в функцииprintfиспользуется спецификатор преобразования%u, что снова вызывает UB. Лучше использовать%td. P.P.S. При вычислении разности двух указателей в некоторых экстремальных случаях возможно переполнение типаstd::ptrdiff_t, что опять таки есть UB. – wololo Dec 30 '23 at 18:58std::ptrdiff_t" - ну это проблема не переполненияstd::ptrdiff_t, а проблема переполнения разрядной сетки в целом. Но честно, я не могу представить себе такие монструозные структуры в реальной жизни)) На практике, их даже не получится использовать, т.к. попытка выделить под них память приведёт к исключениюbad_alloc. И кстати, я однажды объявил структуру не такую экстремально большую, но в ней тоже был массив на несколько сотен мегабайт. Так мой MinGW завис при компиляции)) – LShadow77 Dec 31 '23 at 18:12offsetofдля поля-параметра, который передаётся шаблону. Её суть в этом вопросе: https://stackoverflow.com/questions/22359535/gcc-can-i-use-offsetof-with-templated-pointer-to-member. В ответе рекомендовали разыменованиеnullptr! И это, на мой взгляд, показательный пример того, как разработчики стандарта сами толкают программистов на нарушения их правил и внедрение UB в свой код. К слову, раз в стандарт добавили "указатели" на поля, то могли бы разрешить приводить их кsize_t- получилась бы отличная заменаoffsetof. Но нет, не их путь! – LShadow77 Jan 01 '24 at 06:50