Re[2]: Безопасно ли присваивать один указатель другому?..
От: okman Беларусь https://searchinform.ru/
Дата: 23.03.18 06:10
Оценка: 47 (6)
Здравствуйте, Alexander G, Вы писали:

AG>Здравствуйте, okman, Вы писали:


O>>Или такое присваивание всегда безопасно, даже если сами указатели содержат null или "мусор"?


AG>Я бы не стал присваивать неинициализированный мусор, потому что инструменты статического анализа кода имеют право дважды ругнуться.

AG>Во-первых, мы используем значение неинициализированной переменной.
AG>Во-вторых, мы же потом не разыменовываем целевой указатель, так? Вот и неиспользованное присвоенное значение.

AG>Что до легальности с точки зрения С/С++.

AG>Есть такая штука, как signaling NaN.
AG>Она может привести к тому, что следующее присвоение упадёт:
AG>
AG>float f1;
AG>float f2;
AG>f2 = f1;
AG>

AG>Если под это есть "законодательная база" в С/С++, то, возможно, она применима и к указателям.

Я всегда считал, что присваивание одного указателя другому всегда безопасно, даже если
сами указатели указывают "в никуда". Но вот наткнулся на примерно такой код (сильно упрощен):

#include <cstdio>

struct Base
{
    virtual ~Base() {}
};

struct X1 : public virtual Base {};
struct X2 : public virtual Base {};
struct Child : public X1, public X2 {};

int main()
{
    X1 * p1 = new X1();
    delete p1;
    Base * p2;
    p2 = p1;
    printf("p2 = %p\r\n", (void *)p2);
    return 0;
}

При запуске на VS2015 или VS2008 в режиме Debug программа падает на строке 'p2 = p1':

"Exception thrown at [...] in MyProgram.exe: 0xC0000005: Access violation reading location [...]".

Аналогично ведут себя онлайн-компиляторы:

codepad (C++) — Segmentation fault
http://codepad.org/ZdbdFm98

ideone (C++14) — Runtime error
https://ideone.com/HRCX3A

rextester (C++ gcc) — Invalid memory reference (SIGSEGV)
http://rextester.com/XKG97608

rextester (C++ clang) — Invalid memory reference (SIGSEGV)
http://rextester.com/GAKSE23949

Ключевой момент — виртуальное наследование X1 и X2 от Base.

Хотелось бы понять, насколько такое поведение (т.е. разыменование указателя, возможно висячего,
при выполнении приведений типов по иерархии наследования) легально с точки зрения стандарта C++.
Или же это сугубо implementation-defined?..
Re[3]: Безопасно ли присваивать один указатель другому?..
От: Croessmah  
Дата: 23.03.18 17:27
Оценка: 37 (4) +2
Здравствуйте, okman, Вы писали:
O>Хотелось бы понять, насколько такое поведение (т.е. разыменование указателя, возможно висячего,
O>при выполнении приведений типов по иерархии наследования) легально с точки зрения стандарта C++.
O>Или же это сугубо implementation-defined?..


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


С++14
12.7 Construction and destruction

3. To explicitly or implicitly convert a pointer (a glvalue) referring to an object of class X to a pointer (reference) to a direct or indirect base class B of X, the construction of X and the construction of all of its direct or indirect bases that directly or indirectly derive from B shall have started and the destruction of these classes shall not have completed, otherwise the conversion results in undefined behavior. To form a pointer to (or access the value of) a direct non-static member of an object obj, the construction of obj shall have started and its destruction shall not have completed, otherwise the computation of the pointer value (or accessing the member value) results in undefined behavior.
[Example:

struct A { };
struct B : virtual A { };
struct C : B { };
struct D : virtual A { D(A*); };
struct X { X(A*); };



struct E : C, D, X { //undefined: upcast from E* to A*
    E() : D(this),   //might use path E* → D* → A*
                     // but D is not constructed
                     // D((C*)this), // defined:
                     // E* → C* defined because E() has started
                     // and C* → A* defined because
                     // C fully constructed
    X(this) {        // defined: upon construction of X,      
    }                // C/B/D/A sublattice is fully constructed            
};
— end example ]


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

    Base * p2;
    p2 = p1;
    delete p1;
    printf("p2 = %p\r\n", (void *)p2);//Ok
Re[3]: Безопасно ли присваивать один указатель другому?..
От: zou  
Дата: 23.03.18 22:15
Оценка: 23 (3) +2
Здравствуйте, okman, Вы писали:

На стандарт ссылку не дам, но, думаю, суть в том, что при присваивании p2 = p1 происходит не просто копирование, а преобразование адреса, для которого требуется прочитать RTTI. Если адрес p1 невалидный, то при обращении к RTTI происходит исключение. В случае валидного адреса p1, переменная p2 будет содержать адрес с неким смещением.
Безопасно ли присваивать один указатель другому?..
От: okman Беларусь https://searchinform.ru/
Дата: 22.03.18 17:55
Оценка: 17 (3)
Привет!

Как думаете, возможно ли в C или C++ получить какой-нибудь побочный эффект во время
присваивания одного указателя другому? Т.е., упрощенно говоря, может ли программа упасть
на выполнении простой конструкции типа x = y? Считаем, что x и y — это самые обычные
"сырые" указатели, т.е. не смарт-поинтеры, не классы с переопределенным оператором
присваивания и ничего такого, а просто самые обычные указатели:

SomeType * x;
AnotherType * y;

//
// здесь много разного кода.
// x и y присваиваются всякие значения,
//    а может и не присваиваются...
//

x = y;

Или такое присваивание всегда безопасно, даже если сами указатели содержат null или "мусор"?
Re[7]: Безопасно ли присваивать один указатель другому?..
От: uzhas Ниоткуда  
Дата: 26.03.18 15:27
Оценка: 6 (2) +1
Здравствуйте, AlexGin, Вы писали:

AG>Как он может иметь разные размерности в одном и том же процессе?


рекомендую к прочтению: https://stackoverflow.com/questions/16062651/about-sizeof-of-a-class-member-function-pointer
Re[4]: Безопасно ли присваивать один указатель другому?..
От: AlexGin Беларусь  
Дата: 26.03.18 13:56
Оценка: -3
Здравствуйте, SaZ, Вы писали:

SaZ>Честно прочитал все ссылки. Что-либо по моему вопросу будет?

Вроде выше, в моём ответе, было именно по твоему вопросу (по поводу void*).

SaZ>Интересует, например, чему должен быть равен sizeof указателя на метод?


sizeof указателя на метод — такой же, как sizeof(int*);
В общем — sizeof такой же, как у всех других указателей в приложениях данной архитектуры (то есть 4 байта для x86; 8 байт для x64).

class Foo 
{
public:
    int f(string str) 
    {
        std::cout << "Foo::f()" << std::endl;
        return 1;
    }
};

int main()
{
    int (Foo::*fptr) (string) = &Foo::f;
    std::cout << "sizeof-fptr=" << sizeof(fptr) << std::endl;
    ...
    return 0;
}


P.S. Вот ещё раз даю ту же самую полезную ссылочку:
https://www.codeguru.com/cpp/cpp/article.php/c17401/C-Tutorial-PointertoMember-Function.htm
более толкового описания по данной теме — я не видел.
Отредактировано 26.03.2018 14:03 AlexGin . Предыдущая версия .
Re: Безопасно ли присваивать один указатель другому?..
От: kov_serg Россия  
Дата: 22.03.18 18:58
Оценка: 15 (1) :)
Здравствуйте, okman, Вы писали:

O>Как думаете, возможно ли в C или C++ получить какой-нибудь побочный эффект во время присваивания одного указателя другому?


Если сильно надо, то можно
struct A {
  int *x, *y;
  void fn() {
    x=y;
  }
};

int main(int argc,char** argv) {
    A *a=0;
    a->fn();
    return 0;
}

А вообще дурдом с этими UB. Скоро неправильное количество пробелов будет UB.
При этом компилятор в случае UB молча будет пытаться сгенерировать максимально неожиданный код.
Re[4]: Безопасно ли присваивать один указатель другому?..
От: antonio_banderas Россия  
Дата: 26.03.18 11:23
Оценка: 7 (1) +1
Здравствуйте, zou, Вы писали:

zou>Здравствуйте, okman, Вы писали:


zou>На стандарт ссылку не дам, но, думаю, суть в том, что при присваивании p2 = p1 происходит не просто копирование, а преобразование адреса, для которого требуется прочитать RTTI. Если адрес p1 невалидный, то при обращении к RTTI происходит исключение. В случае валидного адреса p1, переменная p2 будет содержать адрес с неким смещением.


Как раз примерно это же хотел ответить.
При обычном (невиртуальном) наследовании все смещения при "хождении" по иерархии классов (т.е. при преобразовании указателей в пределах иерархии наследования) известны во время компиляции.
А вот при виртуальном наследовании смещение во время компиляции уже неизвестно и его нужно взять из таблицы смещений (или как она называется), к которой обращаемся через vptr, а в случае мертвого объекта vptr уже невалиден. Отсюда и падение.
Отредактировано 26.03.2018 11:28 antonio_v_krasnom . Предыдущая версия .
Re[6]: Безопасно ли присваивать один указатель другому?..
От: AlexGin Беларусь  
Дата: 26.03.18 14:56
Оценка: -2
Здравствуйте, SaZ, Вы писали:
...
SaZ>Лучше вот это объясните:
SaZ>https://ideone.com/3WjDKl
...
Издержки онлайнового ideone.com

У меня в MSVC2015

#include "stdafx.h"
#include <iostream>
#include <string>

struct A
{
    virtual void f() = 0;
};

struct B : A
{
    void f() override {}
};

int main()
{
    const auto p = &B::f;
    const bool ok = sizeof(void *) == sizeof(p);
    if (!ok)
        std::cout << "Not OK"; 

    std::cout << "sizeof(void *) = " << sizeof(void *) << std::endl; 
    std::cout << "sizeof(p) = " << sizeof(p) << std::endl;

    getchar();
    return 0;
}


Вывод для MSVC2015 (x86):
sizeof(void *) = 4
sizeof(p) = 4

Вывод для MSVC2015 (x64):
sizeof(void *) = 8
sizeof(p) = 8

Верю студии!

вывод для http://rextester.com/l/cpp_online_compiler_visual
sizeof(void *) = 8
sizeof(p) = 8

Здесь (в cpp_online_compiler_visual) также вроде логично.

вывод для ideone.com:
аналогично для https://wandbox.org:
аналогично для http://cpp.sh (в режиме Cpp-11):
Not OKsizeof(void *) = 8
sizeof(p) = 16

Насчёт http://rextester.com:
cpp_online_compiler_gcc также:
и cpp_online_compiler_clang:

Not OKsizeof(void *) = 8
sizeof(p) = 16

Странно...
По крайней мере, у меня здесь нет логичного объяснения.
Отредактировано 26.03.2018 15:18 AlexGin . Предыдущая версия . Еще …
Отредактировано 26.03.2018 15:17 AlexGin . Предыдущая версия .
Re[3]: Безопасно ли присваивать один указатель другому?..
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 23.03.18 06:46
Оценка: +1
Здравствуйте, okman, Вы писали:

O>struct X1 : public virtual Base {};

O>struct X2 : public virtual Base {};
O>struct Child : public X1, public X2 {};

O>int main()

O>{
O> X1 * p1 = new X1();
O> delete p1;
O> Base * p2;
O> p2 = p1;
O> printf("p2 = %p\r\n", (void *)p2);
O> return 0;
O>}
O>[/cpp]

O>Ключевой момент — виртуальное наследование X1 и X2 от Base.


Вот поэтому и пишут в книге "C++ для чайников":

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


-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Re[2]: Безопасно ли присваивать один указатель другому?..
От: Alexander G Украина  
Дата: 23.03.18 08:09
Оценка: +1
Здравствуйте, zaufi, Вы писали:

Z>почему-то никто не отметил самого очевидного -- попытки дереференсить любой из указателей это нарушение strict aliasing, т.е. в конечом итоге UB (если не заморачиваться отдельно).


А де там dereference? Мы ж только присвоили.
Русский военный корабль идёт ко дну!
Re: Безопасно ли присваивать один указатель другому?..
От: Teolog  
Дата: 24.03.18 08:16
Оценка: -1
Абсолютно безопасно за исключением случая с указателем на виртуальную функцию, который имеет извращенный тип.
Проблемы могут возникнуть исключительно в момент разименования.
Более того, данная операция атомарна и соответственно потокобезопасна на любой архитектуре у которой размер типа указателя меньше либо равен разрядности процессора.
Возможно на некоторых других то-же, но это надо смотреть документацию.
Так что можно пользоваться невозбранно за исключением хитрых embeded систем у которых память адресуется по банкам.

но правильно такая операция пишеться
TypeA * a;
TypeB * b;
a=reinterpret_cast<TypeA *>(b);
Потому что при простом приравнивании для указателей на родственные классы начинает работать таинственная адресная магия, которая требует возможности их разименования.
Отредактировано 24.03.2018 8:22 Teolog . Предыдущая версия .
Re: Безопасно ли присваивать один указатель другому?..
От: Ops Россия  
Дата: 22.03.18 18:08
Оценка:
Здравствуйте, okman, Вы писали:

O>Как думаете, возможно ли в C или C++ получить какой-нибудь побочный эффект во время

O>присваивания одного указателя другому?

Гонки?
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Re[2]: Безопасно ли присваивать один указатель другому?..
От: okman Беларусь https://searchinform.ru/
Дата: 22.03.18 18:09
Оценка:
Здравствуйте, Ops, Вы писали:

Ops>Гонки?


Гонки кого с кем?
Re[3]: Безопасно ли присваивать один указатель другому?..
От: Ops Россия  
Дата: 22.03.18 18:13
Оценка:
Здравствуйте, okman, Вы писали:

O>Гонки кого с кем?

O>

Указатель не обязан читаться/писаться атомарно, и в каком--то случае может переключиться контекст, и 2-ю часть перезапишут в другом потоке.
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Re: Безопасно ли присваивать один указатель другому?..
От: VladFein США  
Дата: 22.03.18 18:34
Оценка:
Здравствуйте, okman, Вы писали:

O>Считаем, что x и y — это самые обычные

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

O>
O>SomeType * x;
O>AnotherType * y;
O>

O>Или такое присваивание всегда безопасно, даже если сами указатели содержат null или "мусор"?

если "не смарт-поинтеры, не классы с переопределенным оператором присваивания", то можно говорить о
char* x;
char* y;

?
Тогда точно безопасно.
Иначе — в чём разница?
Re[2]: Безопасно ли присваивать один указатель другому?..
От: okman Беларусь https://searchinform.ru/
Дата: 22.03.18 18:41
Оценка:
Здравствуйте, VladFein, Вы писали:

VF>если "не смарт-поинтеры, не классы с переопределенным оператором присваивания", то можно говорить о

VF>
VF>char* x;
VF>char* y;
VF>

VF>?

Да. И вместо char может стоять любой другой тип.

VF>Тогда точно безопасно.


Ок. На всякий случай подождем еще ответов...
Re: Безопасно ли присваивать один указатель другому?..
От: AlexGin Беларусь  
Дата: 22.03.18 18:48
Оценка:
Здравствуйте, okman, Вы писали:

O>Как думаете, возможно ли в C или C++ получить какой-нибудь побочный эффект во время

O>присваивания одного указателя другому? Т.е., упрощенно говоря, может ли программа упасть
O>на выполнении простой конструкции типа x = y? Считаем, что x и y — это самые обычные
O>"сырые" указатели, т.е. не смарт-поинтеры, не классы с переопределенным оператором
O>присваивания и ничего такого, а просто самые обычные указатели:

Переопределение оператора присваивания — здесь НЕ в тему (присваиваются всё-таки указатели, а не сами объекты) — так что этого бояться не нужно.

O>
O>SomeType * x;
O>AnotherType * y;

O>//
O>// здесь много разного кода.
O>// x и y присваиваются всякие значения,
O>//    а может и не присваиваются...
O>//

O>x = y;
O>

O>Или такое присваивание всегда безопасно, даже если сами указатели содержат null или "мусор"?

Здесь главный вопрос:
Как связаны между собой SomeType и AnotherType???
Если это совершенно разные типы (никак не связянные наследованием) — то это ИМХО не безопасно

В общем, я бы сделал так:
if (dynamic_cast<SomeType*>(y))
{
   x = y;
}


Вся проблема в том, что если SomeType и AnotherType никак не связаны между собой по иерархии классов, то само по себе присвоение указателя конечно же пройдет, но он будет указывать на объект СОВСЕМ другого типа (при дальнейшей работе с указателем "x" добро пожаловать в мир UB)...
Отредактировано 22.03.2018 21:23 AlexGin . Предыдущая версия . Еще …
Отредактировано 22.03.2018 21:16 AlexGin . Предыдущая версия .
Отредактировано 22.03.2018 20:55 AlexGin . Предыдущая версия .
Re: Безопасно ли присваивать один указатель другому?..
От: reversecode google
Дата: 22.03.18 18:52
Оценка:
не безопасно же в многопотокеи
упс уже ответили
Re: Безопасно ли присваивать один указатель другому?..
От: Alexander G Украина  
Дата: 22.03.18 19:11
Оценка:
Здравствуйте, okman, Вы писали:

O>Или такое присваивание всегда безопасно, даже если сами указатели содержат null или "мусор"?


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

Что до легальности с точки зрения С/С++.
Есть такая штука, как signaling NaN.
Она может привести к тому, что следующее присвоение упадёт:
float f1;
float f2;
f2 = f1;

Если под это есть "законодательная база" в С/С++, то, возможно, она применима и к указателям.
Русский военный корабль идёт ко дну!
Отредактировано 22.03.2018 19:14 Alexander G . Предыдущая версия .
Re: Безопасно ли присваивать один указатель другому?..
От: andrey.desman  
Дата: 22.03.18 19:41
Оценка:
Здравствуйте, okman, Вы писали:

O>Или такое присваивание всегда безопасно, даже если сами указатели содержат null или "мусор"?


С мусором UB, если там не char-ы. А так, еще можно с указателями на методы подгореть в теории.
Re: Безопасно ли присваивать один указатель другому?..
От: ononim  
Дата: 22.03.18 21:03
Оценка:
O>x = y;
Гипотетически, указатель может представлять собой пару селектор:адрес. И опять же, гипотетически, y может быть переменной гдето в памяти, а x — компилятор решит разместить в регистрах "прямо ща". И далее читаем тут:

If the destination operand is a segment register (DS, ES, FS, GS, or SS), the source operand must be a valid segment selector. In protected mode, moving a segment selector into a segment register automatically causes the segment descriptor information associated with that segment selector to be loaded into the hidden (shadow) part of the segment register. While loading this information, the segment selector and segment descriptor information is validated (see the "Operation" algorithm below).

..то есть, присвоение сегментому регистру значения невалидного селектора (в защищенном режиме сегментные регистры содержат селекторы) вызывает падение.
Но поскольку чуть более чем почти все оси, работающие в защищенном режиме, предоставляют процессу одно плоское адресное пространство, то хранить значение селектора в переменной-указателе смысла нету, потому присвоение указателя не хряпнется.
Но в принципе могут быть более другие архитектуры, у которых например все обращения к памяти могут делаться только через какой нить спец. регистр — указатель, и который к примеру может принимать только выровненное на размер слвоа значение, а попытка присвоения такому регистру невыровненного значения вызовет ошибку защиты.
Как много веселых ребят, и все делают велосипед...
Отредактировано 22.03.2018 21:11 ononim . Предыдущая версия . Еще …
Отредактировано 22.03.2018 21:07 ononim . Предыдущая версия .
Re: Безопасно ли присваивать один указатель другому?..
От: zaufi Земля  
Дата: 22.03.18 21:15
Оценка:
Здравствуйте, okman, Вы писали:

O>Привет!


O>
O>SomeType * x;
O>AnotherType * y;

O>//
O>// здесь много разного кода.
O>// x и y присваиваются всякие значения,
O>//    а может и не присваиваются...
O>//

O>x = y;
O>

O>Или такое присваивание всегда безопасно, даже если сами указатели содержат null или "мусор"?

почему-то никто не отметил самого очевидного -- попытки дереференсить любой из указателей это нарушение strict aliasing, т.е. в конечом итоге UB (если не заморачиваться отдельно).
Re[3]: Безопасно ли присваивать один указатель другому?..
От: night beast СССР  
Дата: 23.03.18 06:27
Оценка:
Здравствуйте, okman, Вы писали:

O>Хотелось бы понять, насколько такое поведение (т.е. разыменование указателя, возможно висячего,

O>при выполнении приведений типов по иерархии наследования) легально с точки зрения стандарта C++.
O>Или же это сугубо implementation-defined?..

ссылок на стандарт не приведу, но чисто логически падение оправдано, т.к. идет обращение по невалидному адресу, который уже может быть возвращен ОС.
Re: Безопасно ли присваивать один указатель другому?..
От: SaZ  
Дата: 23.03.18 09:42
Оценка:
Здравствуйте, okman, Вы писали:

O>Привет!


O>Как думаете, возможно ли в C или C++ получить какой-нибудь побочный эффект во время

O>присваивания одного указателя другому? Т.е., упрощенно говоря, может ли программа упасть
O>на выполнении простой конструкции типа x = y? Считаем, что x и y — это самые обычные
O>"сырые" указатели, т.е. не смарт-поинтеры, не классы с переопределенным оператором
O>присваивания и ничего такого, а просто самые обычные указатели:

O>
O>SomeType * x;
O>AnotherType * y;

O>//
O>// здесь много разного кода.
O>// x и y присваиваются всякие значения,
O>//    а может и не присваиваются...
O>//

O>x = y;
O>

O>Или такое присваивание всегда безопасно, даже если сами указатели содержат null или "мусор"?

А если это указатели на методы? Где-то слышал, что указатель на метод — это далеко не (void *). Если кто-то может — киньте ссылкой на внятное объяснение.
Re[3]: Безопасно ли присваивать один указатель другому?..
От: zaufi Земля  
Дата: 23.03.18 14:15
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Здравствуйте, zaufi, Вы писали:


Z>>почему-то никто не отметил самого очевидного -- попытки дереференсить любой из указателей это нарушение strict aliasing, т.е. в конечом итоге UB (если не заморачиваться отдельно).


AG>А де там dereference? Мы ж только присвоили.


ну в данном коде не видать конечно... но указатели ведь присваивают чтобы когда-нибудть дереференснуть %)
Re[2]: Безопасно ли присваивать один указатель другому?..
От: AlexGin Беларусь  
Дата: 23.03.18 14:53
Оценка:
Здравствуйте, SaZ, Вы писали:

SaZ>А если это указатели на методы? Где-то слышал, что указатель на метод — это далеко не (void *). Если кто-то может — киньте ссылкой на внятное объяснение.


Скажем так: это далеко не (void *), а более сложный тип — вот как первый параметр в _beginthread:
https://msdn.microsoft.com/en-us/library/kdzttdcb.aspx
Справедливости ради: первый параметр в _beginthread — указатель на глобальную функцию или на статический метод класса.

Просто во времена ANSI C, указатель на void считался аналогом универсального указателя.
Вот пример функции сортировки и передачи в неё указателя на "компаратор":
https://www.tutorialspoint.com/c_standard_library/c_function_qsort.htm

А насчёт самих указателей на методы — вот подробнее:
https://www.codeguru.com/cpp/cpp/article.php/c17401/C-Tutorial-PointertoMember-Function.htm

Вот ещё что-то полезное по данной теме:
https://toster.ru/q/240698
Отредактировано 23.03.2018 15:35 AlexGin . Предыдущая версия . Еще …
Отредактировано 23.03.2018 15:20 AlexGin . Предыдущая версия .
Отредактировано 23.03.2018 15:08 AlexGin . Предыдущая версия .
Re[3]: Безопасно ли присваивать один указатель другому?..
От: lastcross  
Дата: 23.03.18 16:57
Оценка:
O>Но вот наткнулся на примерно такой код (сильно упрощен):

Точно падает в присвоении ?? Тут например не хочет падать http://coliru.stacked-crooked.com/a/8d2dea8e83a98c19 и даже так http://coliru.stacked-crooked.com/a/4e8c07ee945250e9
Отредактировано 23.03.2018 16:59 lastcross . Предыдущая версия .
Re[3]: Безопасно ли присваивать один указатель другому?..
От: AlexGin Беларусь  
Дата: 23.03.18 20:10
Оценка:
Здравствуйте, okman, Вы писали:

O>
O>#include <cstdio>

O>struct Base
O>{
O>    virtual ~Base() {}
O>};

O>struct X1 : public virtual Base {};
O>struct X2 : public virtual Base {};
O>struct Child : public X1, public X2 {};

O>int main()
O>{
O>    X1 * p1 = new X1();
O>    delete p1;
O>    Base * p2;
O>    p2 = p1;
O>    printf("p2 = %p\r\n", (void *)p2);
O>    return 0;
O>}
O>

O>При запуске на VS2015 или VS2008 в режиме Debug программа падает на строке 'p2 = p1':
У меня также (на VS2015) данный пример падает на строке 'p2 = p1'

После чего я изменил пример так:
#include <cstdio>

struct Base
{
    virtual ~Base() {}
};

struct X1 : public virtual Base {};
struct X2 : public virtual Base {};
struct Child : public X1, public X2 {};

int main()
{
    X1 * p1 = new X1();
    delete p1;
    X1 * p2; // Эта строка изменена!!!
    p2 = p1;
    printf("p2 = %p\r\n", (void *)p2);
    return 0;
}

...и все падения прекратились!

O>Или же это сугубо implementation-defined?..

+100500
ИМХО похоже именно на это.
Re[4]: Безопасно ли присваивать один указатель другому?..
От: okman Беларусь https://searchinform.ru/
Дата: 24.03.18 04:15
Оценка:
Здравствуйте, Croessmah, Вы писали:

C>Если я правильно понял, то Вы наткнулись на неопределенное поведение при преобразовании указателей:

C>...

Спасибо, именно эта цитата все объясняет:

To explicitly or implicitly convert a pointer (a glvalue) referring to an object of class X to a
pointer (reference) to a direct or indirect base class B of X, the construction of X and the construction
of all of its direct or indirect bases that directly or indirectly derive from B shall have
started and the destruction of these classes shall not have completed, otherwise the conversion
results in undefined behavior.


C>То есть для Вашего случая сначала необходимо сделать преобразование (присваивание) пока объект не уничтожен, и только затем удалять объект:

C>...

Похоже, что разработчики библиотек в курсе про этот нюанс. Вот нашел в исходниках
Boost 1.66.0 следующий комментарий (/boost/smart_ptr/weak_ptr.hpp):
//
//  The "obvious" converting constructor implementation:
//
//  template<class Y>
//  weak_ptr(weak_ptr<Y> const & r): px(r.px), pn(r.pn)
//  {
//  }
//
//  has a serious problem.
//
//  r.px may already have been invalidated. The px(r.px)
//  conversion may require access to *r.px (virtual inheritance).
//
//  It is not possible to avoid spurious access violations since
//  in multithreaded programs r.px may be invalidated at any point.
//

Судя по всему, они подразумевают примерно такой кейс:
shared_ptr<X1> ptr = ...;
weak_ptr<X1> w1(ptr);
ptr.reset();
weak_ptr<Base> w2(w1); // BANG!

Глянул реализацию std::weak_ptr в VS2015 — там тоже учтен этот момент.
Re[4]: Безопасно ли присваивать один указатель другому?..
От: Meyers  
Дата: 24.03.18 04:48
Оценка:
Здравствуйте, Croessmah, Вы писали:

C>Если я правильно понял, то Вы наткнулись на неопределенное поведение при преобразовании указателей:


Цитата из стандарта относится к objects under construction or destruction:
"[class.base.init] and [class.cdtor] (процитированное) describe the behavior of objects during the construction and destruction phases" — (http://eel.is/c++draft/basic.life#4.sentence-4)

Я не говорю, что там не UB, но цитата не та, т.к. там преобразование указателей было не во время создания или удаления объекта.
Re: Безопасно ли присваивать один указатель другому?..
От: Кодт Россия  
Дата: 25.03.18 15:54
Оценка:
Здравствуйте, okman, Вы писали:

O>Как думаете, возможно ли в C или C++ получить какой-нибудь побочный эффект во время

O>присваивания одного указателя другому? Т.е., упрощенно говоря, может ли программа упасть
O>на выполнении простой конструкции типа x = y? Считаем, что x и y — это самые обычные
O>"сырые" указатели, т.е. не смарт-поинтеры, не классы с переопределенным оператором
O>присваивания и ничего такого, а просто самые обычные указатели:
O>Или такое присваивание всегда безопасно, даже если сами указатели содержат null или "мусор"?

Можно получить не идемпотентное поведение.
Derived* d;

Base* b = d;  // up-cast разрешён всегда
Derived* d1 = static_cast<Derived*>(b);  // down-cast должен быть явным
Base* b1 = d1;

assert(d == d1);
assert(b == b1);

Логично?
А вот если так:
https://ideone.com/vp8AOz
#include <iostream>
#include <cassert>
using namespace std;

struct Alfa { char t[1024]; };
struct Base { char u[1024]; };
struct Derived: Alfa, Base { char v[1024]; };

Derived* downcast(Base* b) {
  return static_cast<Derived*>(b);
}

void test_bd(Base* b) {
  Derived* d = downcast(b);
  cout << "bd::" << b << " " << d << endl;
}

void test_dbd(Derived* d) {
  Base* b = d;
  const char* isnull = (b == nullptr) ? "" : "!=nullptr";
  const char* iszero = (intptr_t)(b) == 0 ? "" : "!=zero";
  Derived* d1 = downcast(b);
  cout << "dbd:" << d << " " << b << isnull << iszero << " " << d1 << " " << endl;
  test_bd(b);
}

void test_bdb(Base* b) {
  Derived* d = downcast(b);
  Base* b1 = d;
  cout << "bdb:" << b << " " << d << " " << b1 << endl;
}

int main() {
  test_dbd((intptr_t)0);                  // 0 0 0 / 0 0
  test_bdb((intptr_t)0);                  // 0 0 0
  test_dbd((Derived*)(intptr_t)(-1024));  // 0xfffffffffffffc00 0!=nullptr!=zero 0xfffffffffffffc00 / 0 0
  test_bdb((Base*)(intptr_t)(1024));      // 0x400 0 0
}

Обратите внимание на третий случай. Там компилятор видит 0, но отказывается верить, что это 0.

А всё потому, что адресная арифметика за пределами массива и смещение базы невалидных указателей — неопределённое поведение.

(nullptr и указатель за концом массива — это валидные, но неразыменовываемые)


И это, заметьте, безо всяких там виртуальных наследований, — где попытка даже апкаста для невалидного указателя приведёт к стрельбе по памяти.
struct A { int x; };
 
struct B : virtual A { int y; };
 
int main() {
    B* b = (B*)(intptr_t(100500));
    A* a = b;
    cout << b << " " << a << endl;
}
Перекуём баги на фичи!
Re: Безопасно ли присваивать один указатель другому?..
От: Pavel Dvorkin Россия  
Дата: 25.03.18 16:09
Оценка:
Здравствуйте, okman, Вы писали:

O>Или такое присваивание всегда безопасно, даже если сами указатели содержат null или "мусор"?


Ответ в рамках существующих систем — безопасно.

Ответ формальный — нет. Потому что указатель — это не адрес вообще-то, а лишь нечто такое, применение к которому операции дерефренсирования дает доступ к объекту, на который он указывает.

Механизм этого дерефренсирования может в принципе быть любым.

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

Все это очень далеко от реальности и чисто умозрительно. Но не невозможно в принципе.
With best regards
Pavel Dvorkin
Re[2]: Безопасно ли присваивать один указатель другому?..
От: Meyers  
Дата: 25.03.18 19:39
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Здравствуйте, okman, Вы писали:


O>>Или такое присваивание всегда безопасно, даже если сами указатели содержат null или "мусор"?


PD>Ответ в рамках существующих систем — безопасно.


http://rsdn.org/forum/cpp.applied/7090690.1
Автор: okman
Дата: 23.03.18
Re[3]: Безопасно ли присваивать один указатель другому?..
От: SaZ  
Дата: 26.03.18 08:38
Оценка:
Здравствуйте, AlexGin, Вы писали:

AG>...


AG>Скажем так: это далеко не (void *), а более сложный тип — вот как первый параметр в _beginthread:

AG>...

Честно прочитал все ссылки. Что-либо по моему вопросу будет? Интересует, например, чему должен быть равен sizeof указателя на метод?
Re: Безопасно ли присваивать один указатель другому?..
От: uzhas Ниоткуда  
Дата: 26.03.18 11:06
Оценка:
Здравствуйте, okman, Вы писали:

O>Как думаете, возможно ли в C или C++ получить какой-нибудь побочный эффект во время

O>присваивания одного указателя другому?

если приемник плохо выровнен, то это UB и на практике приводит к bus error

зы. я твой пример уже прочитал, зачётно. просто решил пополнить множество вариантов
Re[4]: Безопасно ли присваивать один указатель другому?..
От: SaZ  
Дата: 26.03.18 13:26
Оценка:
Здравствуйте, zou, Вы писали:

zou>Здравствуйте, okman, Вы писали:


zou>На стандарт ссылку не дам, но, думаю, суть в том, что при присваивании p2 = p1 происходит не просто копирование, а преобразование адреса, для которого требуется прочитать RTTI. Если адрес p1 невалидный, то при обращении к RTTI происходит исключение. В случае валидного адреса p1, переменная p2 будет содержать адрес с неким смещением.


А если кастануть как-то типа (Base *)(void *)p2;? Какие тут могут быть подводные камни? Как я понимаю, для простой печати адреса объекта — так делать можно. Про RTTI я тоже понял, просто уж очень экзотический случай
Re[5]: Безопасно ли присваивать один указатель другому?..
От: SaZ  
Дата: 26.03.18 14:18
Оценка:
Здравствуйте, AlexGin, Вы писали:

AG>

AG>sizeof указателя на метод — такой же, как sizeof(int*);
AG>В общем — sizeof такой же, как у всех других указателей в приложениях данной архитектуры (то есть 4 байта для x86; 8 байт для x64).

Можно не так голословно?

AG>P.S. Вот ещё раз даю ту же самую полезную ссылочку:

AG>https://www.codeguru.com/cpp/cpp/article.php/c17401/C-Tutorial-PointertoMember-Function.htm
AG>более толкового описания по данной теме — я не видел.

Ещё раз. Ссылки я прочитал. Не нужно их дублировать по 100 раз. Лучше вот это объясните:
https://ideone.com/3WjDKl
#include <iostream>
#include <string>
 
struct A
{
    virtual void f() = 0;
};
 
struct B : A
{
    void f() override {}
};
 
int main()
{
    const auto p = &B::f;
    const bool ok = sizeof(void *) == sizeof( p );
    if ( !ok )
        std::cout << "Not OK";
    return 0;
}
Re[5]: Безопасно ли присваивать один указатель другому?..
От: night beast СССР  
Дата: 26.03.18 14:41
Оценка:
Здравствуйте, AlexGin, Вы писали:

SaZ>>Честно прочитал все ссылки. Что-либо по моему вопросу будет?

AG>Вроде выше, в моём ответе, было именно по твоему вопросу (по поводу void*).

SaZ>>Интересует, например, чему должен быть равен sizeof указателя на метод?

AG>
AG>sizeof указателя на метод — такой же, как sizeof(int*);
AG>В общем — sizeof такой же, как у всех других указателей в приложениях данной архитектуры (то есть 4 байта для x86; 8 байт для x64).

это не так.
как минимум на старых студиях под 32 бита этот размер был 8.
с указателем на обычную функцию тоже никаких гарантий не было что можно безопасно к void* кастить.
Re[6]: Безопасно ли присваивать один указатель другому?..
От: AlexGin Беларусь  
Дата: 26.03.18 15:03
Оценка:
Здравствуйте, night beast, Вы писали:

NB>Здравствуйте, AlexGin, Вы писали:


SaZ>>>Честно прочитал все ссылки. Что-либо по моему вопросу будет?

AG>>Вроде выше, в моём ответе, было именно по твоему вопросу (по поводу void*).

SaZ>>>Интересует, например, чему должен быть равен sizeof указателя на метод?

AG>>
AG>>sizeof указателя на метод — такой же, как sizeof(int*);
AG>>В общем — sizeof такой же, как у всех других указателей в приложениях данной архитектуры (то есть 4 байта для x86; 8 байт для x64).

NB>это не так.

Я здесь привёл примеры для MSVC2015 — всё вполне логично (размеры указателей одинаковы).
Здесь у меня под рукой нет другой студии (дома проверю под MSVC2008, но более древних не найду).

NB>как минимум на старых студиях под 32 бита этот размер был 8.

NB>с указателем на обычную функцию тоже никаких гарантий не было что можно безопасно к void* кастить.

Где хоть какое-то объяснение данному феномену?

Указатель — это адрес в памяти.
Как он может иметь разные размерности в одном и том же процессе?
Re[8]: Безопасно ли присваивать один указатель другому?..
От: SaZ  
Дата: 29.03.18 14:31
Оценка:
Здравствуйте, uzhas, Вы писали:

U>Здравствуйте, AlexGin, Вы писали:


AG>>Как он может иметь разные размерности в одном и том же процессе?


U>рекомендую к прочтению: https://stackoverflow.com/questions/16062651/about-sizeof-of-a-class-member-function-pointer


Спасибо.
Я вообще к чему это всё — в техникуме нас учили языку Си. С тех пор я как-то привык что в void* можно положить любой указатель. И был удивлён, что это не так. В одном проекте у нас интроспекция строилась через void*. И вроде как библиотека cpgf на это завязана. Выходит, там либо этот момент учтён, либо мне просто везло (в своих велосипедах я никогда не пользовался void*).
Re[9]: Безопасно ли присваивать один указатель другому?..
От: Croessmah  
Дата: 30.03.18 09:09
Оценка:
Здравствуйте, SaZ, Вы писали:

SaZ>Я вообще к чему это всё — в техникуме нас учили языку Си. С тех пор я как-то привык что в void* можно положить любой указатель. И был удивлён, что это не так.


К слову, в C даже указатель на функцию нельзя преобразовывать в указатель на данные (в т.ч. в void*). Такое преобразование является возможным, но имеет неопределенный результат и не является переносимым. Но, например, POSIX требует чтобы такое преобразование было валидным.
Отредактировано 30.03.2018 9:50 Croessmah . Предыдущая версия .
Re[4]: Безопасно ли присваивать один указатель другому?..
От: alpha21264 СССР  
Дата: 30.03.18 09:46
Оценка:
Здравствуйте, Ops, Вы писали:

Ops>Указатель не обязан читаться/писаться атомарно, и в каком--то случае может переключиться контекст, и 2-ю часть перезапишут в другом потоке.


Какие ужасы вы рассказываете! Что же в таком случае происходит с другими типами данных, например с int-ами?

Течёт вода Кубань-реки куда велят большевики.
Re[10]: Безопасно ли присваивать один указатель другому?..
От: SaZ  
Дата: 30.03.18 11:48
Оценка:
Здравствуйте, Croessmah, Вы писали:

C>Здравствуйте, SaZ, Вы писали:


SaZ>>Я вообще к чему это всё — в техникуме нас учили языку Си. С тех пор я как-то привык что в void* можно положить любой указатель. И был удивлён, что это не так.


C>К слову, в C даже указатель на функцию нельзя преобразовывать в указатель на данные (в т.ч. в void*). Такое преобразование является возможным, но имеет неопределенный результат и не является переносимым. Но, например, POSIX требует чтобы такое преобразование было валидным.


Я правильно понял, что стандарт никак не обязует компиляторы использовать какой-то фиксированный размер указателя для определённого типа данных? Т.е. чисто теоретически возможна ситуация, когда
struct A // ...
{
  virtual void f(){}
};
struct B // ...
{
  virtual void f(){}
};

sizeof( &A::f ) != sizeof( &B::f ); // ?


В общем, если делать свою интроспекцию, то надо делать отдельные типы для хранения 1) указатели на поля, 2) указатели на методы, 3) указатели на виртуальные методы, 4) что-то ещё?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.