LINUX.ORG.RU

[знатокам стандарта С++] законно ли write(fd, &obj, sizeof(obj)) <перезапуск> read(fd, &obj, sizeof(obj)) при условии что...


0

0

... что:

class A {int a;};
class B: public A {int b;};
B object;

Странный вопрос? А вот я у Страуструпа (вроде) читал, что реализация класса В, выбранная разработчиком компилятора, может быть вовсе не обязательно struct B1 { int a; int b; };

Может быть выбрана такая реализация: struct __B2 {int b;}; struct B2 { int a; __B2* __b2; } и для этой реализации write запишет на диск адрес __b2, а потом, при следующем запуске программы, read прочтет уже недействительный адрес в поле __b2.

Где в стандарте написано что-то на эту тему?

>struct __B2 {int b;}; struct B2 { int a; __B2* __b2; }

Полнейшая чушь, компилятор не имеет права выбирать такую реализацию. Оба класса - POD-типы. write/read законны и правильны. В стандарте лазить лень.

JackYF ★★★★
()

Раздел 3.9 [basic.types], пункт 2.

Там сказано, что POD-типы ("plain old data") можно так копировать, в массив char [sizeof(T)] и обратно. Про не-POD стандарт, скорее всего, просто не даёт никаких гарантий (то есть если прошерстить весь стандарт от корки до корки, то ничего найти про это не удастся).

anonymous
()

> Где в стандарте написано что-то на эту тему?

1.8.5 An object of POD type (basic.types) shall occupy contiguous bytes of storage.

Но вопрос хороший. Если полиморфный класс может занимать не неразрывный кусок памяти, то как там рассчитывается sizeof? В общем, it ruines my world.

dilmah ★★★★★
()
Ответ на: комментарий от JackYF

Although it is sometimes thought that "Plain Old Data" is only an informal term that should be avoided in general communications, a POD notion is defined in ISO/IEC standard 14882 for the C++ programming language. A POD type in the standard is either a scalar type or a POD class. POD class has no user-defined copy assignment operator, no user-defined destructor, and no non-static data members that are not themselves PODs. Moreover, POD class must be an aggregate, meaning it has no user-declared constructors, no private nor protected non-static data, no bases and ... <-------- базы у меня есть.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от dilmah

> В общем, it ruines my world.

Потому и спрашиваю. Похоже, это не POD!

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от dilmah

>> Где в стандарте написано что-то на эту тему?

>>1.8.5 An object of POD type (basic.types) shall occupy contiguous bytes of storage.

>Но вопрос хороший. Если полиморфный класс может занимать не неразрывный кусок памяти, то как там рассчитывается sizeof? В общем, it ruines my world.

2 my mind такой реализации не будет никогда.

Absurd ★★★
()
Ответ на: комментарий от JackYF

> Полнейшая чушь, компилятор не имеет права выбирать такую реализацию. Оба класса - POD-типы. write/read законны и правильны. В стандарте лазить лень.

Нет, class B - не POD, потому что у него есть базовый класс. Согласно 8.5.1 [dcl.init.aggr], такие классы не являются aggregate. По определению в 9 [class]: "A POD-struct is an aggregate class". Не aggregate - значит не POD. Поэтому пример в исходном посте правильный.

anonymous
()
Ответ на: комментарий от Absurd

> 2 my mind такой реализации не будет никогда.

ну vtable таки лежит в другом месте. Просто это не влияет на состояние объекта.

dilmah ★★★★★
()
Ответ на: комментарий от dilmah

> Если полиморфный класс может занимать не неразрывный кусок памяти, то как там рассчитывается sizeof? В общем, it ruines my world.

А зачем тебе его sizeof? В стандарте определены какие-то смутные правила на этот счёт (5.3.3 [expr.sizeof), но из них можно только сделать вывод, что sizeof в этом случае практически бесполезен.

anonymous
()
Ответ на: комментарий от dilmah

> Если полиморфный класс может занимать не неразрывный кусок памяти, то как там рассчитывается sizeof? В общем, it ruines my world.

А зачем тебе его sizeof? В стандарте определены какие-то смутные правила на этот счёт (5.3.3 [expr.sizeof), но из них можно только сделать вывод, что sizeof в этом случае практически бесполезен.

anonymous
()
Ответ на: комментарий от dilmah

>> 2 my mind такой реализации не будет никогда.

>ну vtable таки лежит в другом месте. Просто это не влияет на состояние объекта.

Ну и как тогда будет выглядеть скомпилированный код - при апкастинге пробегать всю иерархию до верха? Куча кода написана с расчетом на то при что одиночном наследовании адрес начала базового объекта совпадает с адресом начала производного объекта и static_cast<> не делает ничего кроме локального отключения контроля типов.

Absurd ★★★
()
Ответ на: комментарий от Absurd

> static_cast<> не делает ничего кроме локального отключения контроля типов.

если базовых классов несколько, то static_cast сдвигает указатель..

dilmah ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

самое близкое:

3.9.4 The object representation of an object of type T is the sequence of N unsigned char objects taken up by the object of type T, where N equals sizeof(T). The value representation of an object is the set of bits that hold the value of type T.

dilmah ★★★★★
()
Ответ на: комментарий от Absurd

> Куча кода написана с расчетом на то при что одиночном наследовании адрес начала базового объекта совпадает с адресом начала производного объекта

Что-то не могу придумать пример, как написать так, чтобы при "нелогичной" реализации одиночного наследования код ломался.

anonymous
()
Ответ на: комментарий от Absurd

> Ну и как тогда будет выглядеть скомпилированный код - при апкастинге пробегать всю иерархию до верха?

Не нужно. (хотя вопрос не про это)

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от dilmah

> 3.9.4 The object representation of an object of type T

Это просто определения. Из этого же пункта видно, что value representation полностью содержится в object representation только для POD-типов. Видимо, подразумевается, что для не-под value representation может быть разбросано по разным местам.

anonymous
()
Ответ на: комментарий от dilmah

>> static_cast<> не делает ничего кроме локального отключения контроля типов.

>если базовых классов несколько, то static_cast сдвигает указатель..

В Loki::Tuple множественное наследование применяется для генерации структуры из списка типов - структура рекурсивно наследуется по одному разу от каждого Holder<тип> в списке типов. Александреску утверждает что элементы в таких структурах располагаются в пямяти последовательно. Я уверен что это уже перекочевало в буст и ломать это дело никто не будет.

Absurd ★★★
()
Ответ на: комментарий от anonymous

>> Куча кода написана с расчетом на то при что одиночном наследовании адрес начала базового объекта совпадает с адресом начала производного объекта

>Что-то не могу придумать пример, как написать так, чтобы при "нелогичной" реализации одиночного наследования код ломался.

Derived*->void*->Base*

Absurd ★★★
()
Ответ на: комментарий от anonymous

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

An aggregate is an array or a class (clause 9) with no user-declared constructors (12.1), no private or protected non-static data members (clause 11), no base classes (clause 10), and no virtual functions (10.3).

Так что В -- не агрегат. Дальше имеем:

A POD class is a class that is either a POD-struct or a POD-union.

POD-union отпадает. Остается POD-struct:

A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defined copy assignment operator and no user-defined destructor.

Так что В -- не POD class.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от dilmah

> Если полиморфный класс может занимать не неразрывный кусок памяти, то как там рассчитывается sizeof?

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

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от www_linux_org_ru

>> Derived*->void*->Base*

>ССЗБ

Любая интеракция с Си-кодом содержит такие конверсии.

Наример, при создании треда функции-точке входа передается указатель на инстанс класса Thread и она делает static_cast<Thread*>(handback)->run().

Absurd ★★★
()
Ответ на: комментарий от Absurd

>> Что-то не могу придумать пример, как написать так, чтобы при "нелогичной" реализации одиночного наследования код ломался. > > Derived*->void*->Base*

Нет, это просто стандартная ошибка при использовании C++, которая имеет тенденцию ускользать на большинстве реализаций, когда наследование не множественное, и немедленно вылезает повреждениями памяти, как только добавляется ещё один базовый класс. Сам на такие грабли наступал и на gcc, и на Microsoft Visual C++.

И не надо надеяться на то, что разработчики компиляторов заботятся об ошибках такого рода. Их библия - стандарт, и ничего более.

anonymous
()
Ответ на: комментарий от Absurd

> Derived*->void*->Base*

Такая ошибка даже называется своим именем... сорри, забыл как.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от Legioner

> run_thread(static_cast<Thread*>(thread));
>

> void run_thread(void* obj) {

> Thread *thread = reinterpret_cast<Thread*>(obj);

> thread->run();

> }

>

> Какие тут проблемы?


я бы как раз использовал static_cast оба раза, потому что про это в стандарте явно
написно, что будет работать (5.2.9, пункт 10: "A value of type pointer to object converted to
“pointer to cv void” and back to the original pointer type will have its original value.")

а вот если сначала reinterpret_, а потом static_, то это уже как-то сложновато смотрится.
reinterpret_cast по семантике вообще тут неуместно.

anonymous
()
Ответ на: комментарий от Absurd

> Александреску утверждает что элементы в таких структурах располагаются в пямяти последовательно.

И видимо так и есть в большинстве компиляторов.

Однако вопрос не в этом, а в том, использует ли он эту укладку в памяти? Т.е. адресную арифметику или void* вместо стандартных плюсовых неявных преобразований?

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от anonymous

>>> Что-то не могу придумать пример, как написать так, чтобы при "нелогичной" реализации одиночного наследования код ломался.

>> Derived*->void*->Base*

>Нет, это просто стандартная ошибка при использовании C++, которая имеет тенденцию ускользать на большинстве реализаций, когда наследование не множественное, и немедленно вылезает повреждениями памяти, как только добавляется ещё один базовый класс.

Однако кастовать указатель на объект к void* стандарт позволяет. И обратно тоже.

>И не надо надеяться на то, что разработчики компиляторов заботятся об ошибках такого рода. Их библия - стандарт, и ничего более.

У MS? библия - стандарт? Они ИМХО до сих пор используют C-style cast повсеместно. GCC тоже не будут изменять топологию размещения элементов в классах, поскольку подавляющее большинство молодых FOSS-проектов написано через Ж и напичкано хаками.

Absurd ★★★
()
Ответ на: комментарий от anonymous

> я бы как раз использовал static_cast оба раза, потому что про это в стандарте явно написно, что будет работать (5.2.9, пункт 10: "A value of type pointer to object converted to “pointer to cv void” and back to the original pointer type will have its original value.")

да, точно.

dilmah ★★★★★
()
Ответ на: комментарий от Absurd

> Они ИМХО до сих пор используют C-style cast повсеместно.

у C-style cast четко описанное в стандарте значение. Какой из const_cast, static_cast, *_cast он означает в какой ситуации

dilmah ★★★★★
()
Ответ на: комментарий от dilmah

> у C-style cast четко описанное в стандарте значение. Какой из const_cast, static_cast, *_cast он означает в какой ситуации

поэтому часто лучше использовать как раз C-style cast, чтобы компилятор автоматически вывел какой *_cast использовать, чтобы не было таких ошибок как выше использовали reinterpret_cast вместо static_cast.

dilmah ★★★★★
()
Ответ на: комментарий от dilmah

>> у C-style cast четко описанное в стандарте значение. Какой из const_cast, static_cast, *_cast он означает в какой ситуации

>поэтому часто лучше использовать как раз C-style cast, чтобы компилятор автоматически вывел какой *_cast использовать, чтобы не было таких ошибок как выше использовали reinterpret_cast вместо static_cast.

AFAIK C-style cast - это reinterpret_cast + const_cast.

Absurd ★★★
()
Ответ на: комментарий от Absurd

> Однако кастовать указатель на объект к void* стандарт позволяет.

Да -- неявно: void* memory="abcd";

> И обратно тоже.

А обратно -- только явно, через (void*), чтобы тебя заставить задуматься.

Кстати: в этом "заставить задуматься" есть дыра -- именно эта пара write-read. По-хорошему там должна учиваться (не)константность -- тогда дыру можно прикрыть.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от Absurd

> AFAIK C-style cast - это reinterpret_cast + const_cast.

нет. Он может означать любой *_cast кроме dynamic_cast.

5.4 - Explicit type conversion (cast notation) [expr.cast]

dilmah ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

>> Однако кастовать указатель на объект к void* стандарт позволяет.

>Да -- неявно: void* memory="abcd";

Есть еще dynamic_cast<void*>(..) - он позволяет найти начало объекта в памяти если наследование множественное.

Absurd ★★★
()
Ответ на: комментарий от Absurd

> AFAIK C-style cast - это reinterpret_cast + const_cast.

вынужден опять отвечать чётким "нет". C-style-cast отображается в C++-касты в следующем порядке (используется первый подходящий вариант), 5.4, пункт 5:

— a const_cast (5.2.11), — a static_cast (5.2.9), — a static_cast followed by a const_cast, — a reinterpret_cast (5.2.10), or — a reinterpret_cast followed by a const_cast,

Про Александреску скажу, что он реализация Loki::Tuple выполнена в полном соответствии со стандартом, а про размещение в памяти он говорил, вероятно, только чтобы обосновать, что производительность будет адекватной практически во всех случаях. Это аргумент за то, что выгоды от использования tuple перевешивают риск падения скорости при использовании "необычного" компилятора. Так что как пример к этому обсуждению это не годится.

anonymous
()
Ответ на: комментарий от dilmah

>> AFAIK C-style cast - это reinterpret_cast + const_cast.

>нет. Он может означать любой *_cast кроме dynamic_cast.

Конверсии типа void (*)(void*) -> void (*)(const CSomeType&) с помощью C-Style cast я тоже у них видел.

Absurd ★★★
()
Ответ на: комментарий от Absurd

> Конверсии типа void (*)(void*) -> void (*)(const CSomeType&) с помощью C-Style cast я тоже у них видел.

Это подразумевает reinterpret_cast и разрешено при условии, что void (*) (void*) был получен через каст от void (*) const CSomeType&) (5.2.10, пункт 6).

Absurd, может хватит "примеров"? :)

anonymous
()
Ответ на: комментарий от anonymous

>> Конверсии типа void (*)(void*) -> void (*)(const CSomeType&) с помощью C-Style cast я тоже у них видел.

>Это подразумевает reinterpret_cast и разрешено при условии, что void (*) (void*) был получен через каст от void (*) const CSomeType&) (5.2.10, пункт 6).

AFAIK в том месте Win32 С API в этот калбэк передавало LPSOMETYPE который видимо был бинарно совместим с бинарным форматом CSomeType.

Absurd ★★★
()
Ответ на: комментарий от Absurd

То есть конверсия была обратной, наглючил - void (*)(const CSomeType&) -> void (*)(void*)

Absurd ★★★
()
Ответ на: комментарий от Absurd

> AFAIK в том месте Win32 С API в этот калбэк передавало LPSOMETYPE который видимо был бинарно совместим с бинарным форматом CSomeType.

AFAIU это всегда reinterpret_cast, так как если у функций разные аргументы (и абсолютно не важно совместимы ли сами эти аргументы), то может быть только reinterpret_cast с неопределенным в стандарте поведением.

dilmah ★★★★★
()
Ответ на: комментарий от Absurd

>>Что-то не могу придумать пример, как написать так, чтобы при "нелогичной" реализации одиночного наследования код ломался.

> Derived*->void*->Base*

В2 от такого НЕ сломается. Потому что все эти адреса будут (побитово) одинаковы. (А методы класса В2 будут лезть к b через указатель __b, и это можно оптимизировать легко, так как он не меняется).

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от www_linux_org_ru

> В2 от такого НЕ сломается.

Ну и что, а если такая реализация, то сломается: class B { A* parent; int b; };
Неправильно через void* к базе приводить, и баста. Ты же сам уже говорил об этом.

anonymous
()
Ответ на: комментарий от anonymous

> Ну и что, а если такая реализация, то сломается: class B { A* parent; int b; };

А я такую не предлагал :-) А вот Абсурд сказал что моя реализация В2 сломается -- я ему и говорю, что НЕ сломается

> Неправильно через void* к базе приводить, и баста. Ты же сам уже говорил об этом.

Неправильно, я не спорю. Как эта ошибка называется?

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Зачем я все это затеял -- хотел удостоверится, что альтернативные реализации объектной модели плюсов возможны (например В2). После этого имеет смысл различать саму объектную модель и ее реализацию и ДАЖЕ задуматься о смене реализации модели.

www_linux_org_ru ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.