Почему Вы не можете использовать offsetof на структурах не-POD в C++?

Несколько выходят, хорошо, если Вы управляете им хорошо

, первый шаг должен определить причины выхода. Мой обычно - что-то вроде этого:
1. Никакая потребность выполнить функцию
2. Ошибка найдена
3. Раннее завершение
4. Нормальное завершение
я предполагаю, что можно сгруппироваться "1. Никакая потребность выполнить функцию" в "3. Раннее завершение" (очень раннее завершение, если Вы будете).

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

function foo (input, output, exit_status)

  exit_status == UNDEFINED
  if (check_the_need_to_execute == false) then
    exit_status = NO_NEED_TO_EXECUTE  // reason #1 
    exit

  useful_work

  if (error_is_found == true) then
    exit_status = ERROR               // reason #2
    exit
  if (need_to_go_further == false) then
    exit_status = EARLY_COMPLETION    // reason #3
    exit

  more_work

  if (error_is_found == true) then
    exit_status = ERROR
  else
    exit_status = NORMAL_COMPLETION   // reason #4

end function

, Очевидно, если это выгодно для перемещения глыбы работы на иллюстрации выше в отдельную функцию, необходимо сделать так.

, Если Вы хотите, можно быть более конкретными со статусом выхода, скажем, с несколькими кодами ошибки и ранними кодами завершения для точного определения причины (или даже местоположение) выхода.

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

49
задан Morwenn 20 February 2014 в 03:20
поделиться

8 ответов

Short answer: offsetof is a feature that is only in the C++ standard for legacy C compatibility. Therefore it is basically restricted to the stuff than can be done in C. C++ supports only what it must for C compatibility.

As offsetof is basically a hack (implemented as macro) that relies on the simple memory-model supporting C, it would take a lot of freedom away from C++ compiler implementors how to organize class instance layout.

The effect is that offsetof will often work (depending on source code and compiler used) in C++ even where not backed by the standard - except where it doesn't. So you should be very careful with offsetof usage in C++, especially since I do not know a single compiler that will generate a warning for non-POD use... Modern GCC and Clang will emit a warning if offsetof is used outside the standard (-Winvalid-offsetof).

Edit: As you asked for example, the following might clarify the problem:

#include <iostream>
using namespace std;

struct A { int a; };
struct B : public virtual A   { int b; };
struct C : public virtual A   { int c; };
struct D : public B, public C { int d; };

#define offset_d(i,f)    (long(&(i)->f) - long(i))
#define offset_s(t,f)    offset_d((t*)1000, f)

#define dyn(inst,field) {\
    cout << "Dynamic offset of " #field " in " #inst ": "; \
    cout << offset_d(&i##inst, field) << endl; }

#define stat(type,field) {\
    cout << "Static offset of " #field " in " #type ": "; \
    cout.flush(); \
    cout << offset_s(type, field) << endl; }

int main() {
    A iA; B iB; C iC; D iD;
    dyn(A, a); dyn(B, a); dyn(C, a); dyn(D, a);
    stat(A, a); stat(B, a); stat(C, a); stat(D, a);
    return 0;
}

This will crash when trying to locate the field a inside type B statically, while it works when an instance is available. This is because of the virtual inheritance, where the location of the base class is stored into a lookup table.

While this is a contrived example, an implementation could use a lookup table also to find the public, protected and private sections of a class instance. Or make the lookup completely dynamic (use a hash table for fields), etc.

The standard just leaves all possibilities open by restricting offsetof to POD (IOW: no way to use a hash table for POD structs... :)

Just another note: I had to reimplement offsetof (here: offset_s) for this example as GCC actually errors out when I call offsetof for a field of a virtual base class.

36
ответ дан 7 November 2019 в 11:46
поделиться

Думаю, ваш класс соответствует определению POD в C ++ 0x. g ++ реализовал некоторые из c ++ 0x в своих последних выпусках. Я думаю, что VS2008 также содержит некоторые биты C ++ 0x.

Из статьи c ++ 0x в wikipedia

C ++ 0x ослабит некоторые правила в отношении определения POD.

A класс / структура считается POD, если он тривиален, стандартен и если все его нестатические члены POD.

Определен тривиальный класс или структура. как тот, который:

  1. Имеет тривиальный конструктор по умолчанию. Это может использовать значение по умолчанию синтаксис конструктора (SomeConstructor () = default;).
  2. Имеет тривиальный конструктор копирования, который может использовать синтаксис по умолчанию.
  3. Имеет тривиальный оператор присваивания копии, который может использовать значение по умолчанию синтаксис.
  4. Имеет тривиальный деструктор, который не должен быть виртуальным.

Класс или структура стандартного макета - это определяется как один, который:

  1. Имеет только нестатические элементы данных, которые имеют тип стандартной компоновки.
  2. Имеет одинаковый контроль доступа (общедоступный, частный, защищенный) для всех нестатические члены
  3. Не имеет виртуальных функций
  4. Не имеет виртуальных базовых классов
  5. Имеет только базовые классы стандартного типа
  6. Не имеет базовых классов того же типа, что и первый определенный не- статический member
  7. Либо не имеет базовых классов с нестатическими членами, либо не имеет нестатические элементы данных в большинстве производный класс и не более одной базы класс с нестатическими членами. В сущность, может быть только один класс в иерархии этого класса, который имеет нестатические члены.
2
ответ дан 7 November 2019 в 11:46
поделиться

Если вы добавите, например, виртуальный пустой деструктор:

virtual ~Foo() {}

Ваш класс станет «полиморфным», то есть у него будет скрытое поле-член, которое является указателем на «vtable» который содержит указатели на виртуальные функции.

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

-1
ответ дан 7 November 2019 в 11:46
поделиться

Для определения данных POD структуры, вот и объяснение [уже опубликовано в другом сообщении в Stack Overflow]

Что такое типы POD в C ++?

Теперь, переходя к вашему коду, он работает нормально, как и ожидалось. Это потому, что вы пытаетесь найти offsetof () для общедоступных членов вашего класса, что является допустимым.

Пожалуйста, дайте мне знать, правильный вопрос, если моя точка зрения выше, не проясняет ваши сомнения.

1
ответ дан 7 November 2019 в 11:46
поделиться

Обычно, когда вы спрашиваете « почему что-то не определено », вы получаете ответ «, потому что так сказано в стандарте ». Обычно рациональное решение связано с одной или несколькими причинами, например:

  • трудно обнаружить статически, в каком случае вы являетесь.

  • Угловые случаи трудно определить, и никто не позаботился об определении особых случаев;

  • его использование в основном покрывается другими функциями;

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

Возвращаясь к смещению, вторая причина, вероятно, является доминирующей. Если вы посмотрите на C ++ 0X, где стандарт ранее использовал POD, теперь он использует «стандартный макет», «совместимый макет», «POD». позволяя более изысканные случаи. А для offsetof теперь нужны классы «стандартного макета», а именно в тех случаях, когда комитет не хотел принудительно разрабатывать макет.

Вы также должны учитывать общее использование offsetof (), которое заключается в получении значения поле, когда у вас есть указатель void * на объект. Множественное наследование - виртуальное или нет - проблематично для такого использования.

6
ответ дан 7 November 2019 в 11:46
поделиться

Готов поспорить, вы скомпилируете это с помощью VC ++. Теперь попробуйте с g ++ и посмотрите, как это работает ...

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

-2
ответ дан 7 November 2019 в 11:46
поделиться

В C ++ вы можете получить относительное смещение следующим образом:

class A {
public:
  int i;
};

class B : public A {
public:
  int i;
};

void test()
{
  printf("%p, %p\n", &A::i, &B::i); // edit: changed %x to %p
}
-2
ответ дан 7 November 2019 в 11:46
поделиться

Bluehorn's answer is correct, but for me it doesn't explain the reason for the problem in simplest terms. The way I understand it is as follows:

If NonPOD is a non-POD class, then when you do:

NonPOD np;
np.field;

the compiler does not necessarily access the field by adding some offset to the base pointer and dereferencing. For a POD class, the C++ Standard constrains it to do that(or something equivalent), but for a non-POD class it does not. The compiler might instead read a pointer out of the object, add an offset to that value to give the storage location of the field, and then dereference. This is a common mechanism with virtual inheritance if the field is a member of a virtual base of NonPOD. But it is not restricted to that case. The compiler can do pretty much anything it likes. It could call a hidden compiler-generated virtual member function if it wants.

In the complex cases, it is obviously not possible to represent the location of the field as an integer offset. So offsetof is not valid on non-POD classes.

In cases where your compiler just so happens to store the object in a simple way (such as single inheritance, and normally even non-virtual multiple inheritance, and normally fields defined right in the class that you're referencing the object by as opposed to in some base class), then it will just so happen to work. There are probably cases which just so happen to work on every single compiler there is. This doesn't make it valid.

Appendix: how does virtual inheritance work?

With simple inheritance, if B is derived from A, the usual implementation is that a pointer to B is just a pointer to A, with B's additional data stuck on the end:

A* ---> field of A  <--- B*
        field of A
        field of B

With simple multiple inheritance, you generally assume that B's base classes (call 'em A1 and A2) are arranged in some order peculiar to B. But the same trick with the pointers can't work:

A1* ---> field of A1
         field of A1
A2* ---> field of A2
         field of A2

A1 and A2 "know" nothing about the fact that they're both base classes of B. So if you cast a B* to A1*, it has to point to the fields of A1, and if you cast it to A2* it has to point to the fields of A2. The pointer conversion operator applies an offset. So you might end up with this:

A1* ---> field of A1 <---- B*
         field of A1
A2* ---> field of A2
         field of A2
         field of B
         field of B

Then casting a B* to A1* doesn't change the pointer value, but casting it to A2* adds sizeof(A1) bytes. This is the "other" reason why, in the absence of a virtual destructor, deleting B through a pointer to A2 goes wrong. It doesn't just fail to call the destructor of B and A1, it doesn't even free the right address.

Anyway, B "knows" where all its base classes are, they're always stored at the same offsets. So in this arrangement offsetof would still work. The standard doesn't require implementations to do multiple inheritance this way, but they often do (or something like it). So offsetof might work in this case on your implementation, but it is not guaranteed to.

Now, what about virtual inheritance? Suppose B1 and B2 both have A as a virtual base. This makes them single-inheritance classes, so you might think that the first trick will work again:

A* ---> field of A   <--- B1* A* ---> field of A   <--- B2* 
        field of A                    field of A
        field of B1                   field of B2

But hang on. What happens when C derives (non-virtually, for simplicity) from both B1 and B2? C must only contain 1 copy of the fields of A. Those fields can't immediately precede the fields of B1, and also immediately precede the fields of B2. We're in trouble.

So what implementations might do instead is:

// an instance of B1 looks like this, and B2 similar
A* --->  field of A
         field of A
B1* ---> pointer to A 
         field of B1

Although I've indicated B1* pointing to the first part of the object after the A subobject, I suspect (without bothering to check) the actual address won't be there, it'll be the start of A. It's just that unlike simple inheritance, the offsets between the actual address in the pointer, and the address I've indicated in the diagram, will never be used unless the compiler is certain of the dynamic type of the object. Instead, it will always go through the meta-information to reach A correctly. So my diagrams will point there, since that offset will always be applied for the uses we're interested in.

The "pointer" to A could be a pointer or an offset, it doesn't really matter. In an instance of B1, created as a B1, it points to (char*)this - sizeof(A), and the same in an instance of B2. But if we create a C, it can look like this:

A* --->  field of A
         field of A
B1* ---> pointer to A    // points to (char*)(this) - sizeof(A) as before
         field of B1
B2* ---> pointer to A    // points to (char*)(this) - sizeof(A) - sizeof(B1)
         field of B2
C* ----> pointer to A    // points to (char*)(this) - sizeof(A) - sizeof(B1) - sizeof(B2)
         field of C
         field of C

So to access a field of A using a pointer or reference to B2 requires more than just applying an offset. We must read the "pointer to A" field of B2, follow it, and only then apply an offset, because depending what class B2 is a base of, that pointer will have different values. There is no such thing as offsetof(B2,field of A): there can't be. offsetof will never work with virtual inheritance, on any implementation.

43
ответ дан 7 November 2019 в 11:46
поделиться
Другие вопросы по тегам:

Похожие вопросы: