Russian Qt Forum

Программирование => С/C++ => Тема начата: OKTA от Сентябрь 30, 2014, 17:19



Название: Прелестно
Отправлено: OKTA от Сентябрь 30, 2014, 17:19
Нашел прелестную вещь, о которой не знал  :D
Как думаете, что вылетит в дебаг?  ;D
Код
C
class Foo
{
public:
   Foo() { qDebug() << "Foo()"; }
   void test() { qDebug() << "AHAHAHA";}
   ~Foo() { qDebug() << "~Foo";}
};
 
int main(int argc, char *argv[])
{
   Foo *foo = (Foo*)28;
   foo->test();
   return 0;
}

Даже так работает. Ломает мое представление о мире.
Код
C++ (Qt)
Foo *foo;
   foo->test();


Название: Re: Прелестно
Отправлено: Bepec от Сентябрь 30, 2014, 17:28
Я пару раз так тестировал. Ничего особенного, просто не создавайте переменных и ничего не вылетит :)

PS жду аргументированных объяснений почему так. Ибо сам не знаю, но пользуюсь :D


Название: Re: Прелестно
Отправлено: OKTA от Сентябрь 30, 2014, 17:29
Да это из серии "вопросы на собеседовании по С++"  ;D
Как вызвать не статическую функцию без создания экземпляра класса?

P.S. Сам не знаю, Верес  ;D


Название: Re: Прелестно
Отправлено: Bepec от Сентябрь 30, 2014, 17:37
Можешь ещё жутче поизгаляться. Можно написать полностью консольное приложение на пустом указателе. Главное я уже написал - не создавать переменных :)

PS лично у меня пустота на месте знаний - как собственно работает вызов функции класса. Нигде этот момент не акцентируется :)


Название: Re: Прелестно
Отправлено: Old от Сентябрь 30, 2014, 17:42
Нужно вспомнить, что из себя представляет метод класса и как ему передаётся this. Ничего удивительного нет. Попробуйте в методе test распечатывать значение this и все станет понятно. :)


Название: Re: Прелестно
Отправлено: OKTA от Сентябрь 30, 2014, 17:45
так что запишешь в Foo *foo, то и распечатает) Ставишь 0 - дает 0  ??? Понятнее не стало  ;D


Название: Re: Прелестно
Отправлено: Old от Сентябрь 30, 2014, 17:49
так что запишешь в Foo *foo, то и распечатает) Ставишь 0 - дает 0  ??? Понятнее не стало  ;D
Метод класса это обычная функция, которой первым параметром передаётся адрес размещения объекта (this). Ваша функция ничего ужасного с объектом не делает, вот если вы по адресу писать начнете, тогда начнутся чудеса.


Название: Re: Прелестно
Отправлено: Bepec от Сентябрь 30, 2014, 17:57
Теперь пустота заполнилась :) Благодарю.

Правильно я говорил что программирование - на 90% обман :)

to OKTA: собственно не создавая переменные в классе, мы его не трогаем получается. Так что обращения по невалидному нет. :)


Название: Re: Прелестно
Отправлено: _Bers от Сентябрь 30, 2014, 21:00
Метод класса это обычная функция, которой первым параметром передаётся адрес размещения объекта (this). Ваша функция ничего ужасного с объектом не делает, вот если вы по адресу писать начнете, тогда начнутся чудеса.

Это не совсем так.

По стандарту обращение к функциям-членам класса по невалидному указателю - это UB.
А это значит, что программу может заглючить независимо от того, что делает функция-член.

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

Вообще, на плюсах есть четкое разделение на так называемые "сишные структуры", они же - POD-типы.
И на "полноценные классы".

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

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

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

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


Название: Re: Прелестно
Отправлено: Bepec от Сентябрь 30, 2014, 21:06
Можно сократить до одного предложения.

Использовать сейчас можно, но никто не даёт гарантий. Собственно как и любую возможность с++ :)


Название: Re: Прелестно
Отправлено: Old от Сентябрь 30, 2014, 21:08
Использовать сейчас можно, но никто не даёт гарантий.
Не представляю где это можно использовать. :)


Название: Re: Прелестно
Отправлено: Bepec от Сентябрь 30, 2014, 21:22
Ну лично я использовал когда строил архитектуру. Можно наплодить десятки и сотни классов без инициализации и проверить собственно архитектурку. И нет нужды описывать всё.


Название: Re: Прелестно
Отправлено: Old от Сентябрь 30, 2014, 21:28
Ну лично я использовал когда строил архитектуру. Можно наплодить десятки и сотни классов без инициализации и проверить собственно архитектурку. И нет нужды описывать всё.
::)
Не обижайтесь, но вы написали просто набор слов. :)
Что такое "десятки и сотни классов без инициализации" и как с помощью этого можно проверить "архитектурку"?


Название: Re: Прелестно
Отправлено: _Bers от Сентябрь 30, 2014, 21:39
Можно сократить до одного предложения.

Использовать сейчас можно, но никто не даёт гарантий. Собственно как и любую возможность с++ :)

Стандарт гарантирует вам корректность некоторых возможностей.
И соответственно компиляторы, которые действуют по стандарту.

Никто не дает никаких гарантий на возможности, которые либо вообще не были описаны в стандарте, либо явно там обозначены, как UB.

Ситуация приведенная в сабже описана в стандарте, как UB.


Название: Re: Прелестно
Отправлено: navrocky от Сентябрь 30, 2014, 22:08
Использовать сейчас можно, но никто не даёт гарантий.
Не представляю где это можно использовать. :)

Код
C++ (Qt)
class MyClass
{
public:
   QString toString() const
   {
       if (this)
           return field_;
       else
           return QString();
   }
private:
   QString field_;
};
 


Название: Re: Прелестно
Отправлено: Bepec от Сентябрь 30, 2014, 22:22
Мдамс. Берётся в уме класс с множеством сложных полей. С него лепим заглушку из методов, возвращающих одно и то же. И вуаля у нас N-ное количество классов, на которых можно испытывать что угодно.

А если делать стандартно, то нужно написать такой же класс заглушку, инициализировать все его поля и создавать с помощью new.


Название: Re: Прелестно
Отправлено: _Bers от Сентябрь 30, 2014, 22:57
Использовать сейчас можно, но никто не даёт гарантий.
Не представляю где это можно использовать. :)

Код
C++ (Qt)
class MyClass
{
public:
   QString toString() const
   {
       if (this)
           return field_;
       else
           return QString();
   }
private:
   QString field_;
};
 

Ну вообще это боян. Ну да ладно...

1. проверка гарантированно сломается, если использовать множественное наследование.

2. this не может быть нулевым, иначе это - UB.
Компилятор закладывается на то, что программист не дибил, а значит он не сделает UB, а значит this никогда нулем не будет, а значит на нуль проверять не обязательно - и выпиливает эту проверку.

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

3. А вообще данный код - это классика говнокода. И мусолил эту тему все, кому не лень.
Если кому нибудь захочется подробностей - загуглите статью на хабре.


Название: Re: Прелестно
Отправлено: _Bers от Сентябрь 30, 2014, 23:05
Мдамс. Берётся в уме класс с множеством сложных полей. С него лепим заглушку из методов, возвращающих одно и то же. И вуаля у нас N-ное количество классов,

Которые не нужны. Сама по себе идея налепить множество классов сильно смахивает на "архитектуру ради архитектуры", ну или ооп головного мозга.

А если делать стандартно,

То пишется код, который иллюстрирует "как это должно работать".
Грубо говоря, примеры использования вашего механизма пишутся ещё до того,
как вы начнете создавать сам механизм.

И только после того, как был выработан дизайн, пишется реализация.

На этом кстати, базируется техника программирования TDD.

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

И тогда окажется, что 100500 непонятных классов, которые непонятно как использовать - не нужны.

Это конечно моё ИМХО.




Название: Re: Прелестно
Отправлено: Bepec от Сентябрь 30, 2014, 23:12
Вот именно что имхо :)

И немного лирики напоследок - в любых словах есть смысл. И если человек хочет - он его увидит :)


Название: Re: Прелестно
Отправлено: Old от Октябрь 01, 2014, 06:37
И немного лирики напоследок - в любых словах есть смысл. И если человек хочет - он его увидит :)
Если бы он был, то вы уже давно набросали бы примерчик на c++ с демонстрацией того, как это удобно использовать, но пока только поток слов. :)


Название: Re: Прелестно
Отправлено: Old от Октябрь 01, 2014, 07:12
Код
C++ (Qt)
class MyClass
{
public:
   QString toString() const
   {
       if (this)
           return field_;
       else
           return QString();
   }
private:
   QString field_;
};
 

И когда такой класс мне сможет пригодиться? :)


Название: Re: Прелестно
Отправлено: Johnik от Октябрь 01, 2014, 07:18
И как отработает этот класс в подобном случае:
Код
C++ (Qt)
   Foo *foo = (Foo*)28;
   foo->test();


Название: Re: Прелестно
Отправлено: gil9red от Октябрь 01, 2014, 07:45
И как отработает этот класс в подобном случае:
Код
C++ (Qt)
   Foo *foo = (Foo*)28;
   foo->test();

Падением программы? :) если, вообще, такое скомпилируется :)


Название: Re: Прелестно
Отправлено: OKTA от Октябрь 01, 2014, 09:16
Скомпилируется даже с нулевым указателем, но да, вылетит)


Название: Re: Прелестно
Отправлено: gil9red от Октябрь 01, 2014, 09:19
Скомпилируется даже с нулевым указателем, но да, вылетит)

Ну что ж поделать, компилятор, по умолчанию, уверен, что программист имеет прямые руки и они из правильного места она растут, поэтому и не ругается на такое приведение :)


Название: Re: Прелестно
Отправлено: OKTA от Октябрь 01, 2014, 09:19
И тихо смеется, видя, как программист отстреливает себе ногу  ;D


Название: Re: Прелестно
Отправлено: GreatSnake от Октябрь 01, 2014, 10:18
В свете выше сказанного растолкуйте корявость сего хака
Код
C++ (Qt)
bool QUApplication::notify( QObject* o, QEvent* e )
{
...
case QEvent::KeyPress:
if( w->keyboardGrabber() == w )
{
class _QWidgetP_ : public QWidget
{
public:
_QWidgetP_( QWidget* p = 0, Qt::WindowFlags f = 0 ) : QWidget( p, f ) {}
bool event( QEvent* e )
{
return QWidget::event( e );
}
};
return ( (_QWidgetP_*) w )->event( e );
}
...
}
 

PS. Сей хак понадобился, чтобы обойти Qt-ишный баг, который позволяет отрабатывать QShortcut-ам при клавиатурном грабе.
PPS. QWidget::event() - protected.


Название: Re: Прелестно
Отправлено: Old от Октябрь 01, 2014, 17:28
Так проще:

Код
C++ (Qt)
bool QUApplication::notify( QObject* o, QEvent* e )
{
...
case QEvent::KeyPress:
if( QWidget::keyboardGrabber() == w )   // keyboardGrabber статический же. :)
{
return w->QWidget::event( e );
}
...
}
 


Название: Re: Прелестно
Отправлено: Bepec от Октябрь 01, 2014, 17:35
А пояснить зачем?


Название: Re: Прелестно
Отправлено: Old от Октябрь 01, 2014, 17:43
А пояснить зачем?
Архитектурку же проверить. :)


Название: Re: Прелестно
Отправлено: GreatSnake от Октябрь 01, 2014, 17:53
Так проще:

Код
C++ (Qt)
bool QUApplication::notify( QObject* o, QEvent* e )
{
...
case QEvent::KeyPress:
if( QWidget::keyboardGrabber() == w )   // keyboardGrabber статический же. :)
{
return w->QWidget::event( e );
}
...
}
 


Конечно было проще, если бы не
Код
C++ (Qt)
virtual bool QWidget::event(QEvent*)» is protected


Название: Re: Прелестно
Отправлено: Old от Октябрь 01, 2014, 17:58
Конечно было проще, если бы не
Код
C++ (Qt)
virtual bool QWidget::event(QEvent*)» is protected
А в QObject он паблик, я туда заглянул.


Название: Re: Прелестно
Отправлено: Bepec от Октябрь 01, 2014, 18:35
Old - знать и не говорить, это подло по отношению к другим :D То, что мои слова до вашего разума не добились - не повод ехидничать, хотяя... Ваше право.


Название: Re: Прелестно
Отправлено: Old от Октябрь 01, 2014, 18:40
Old - знать и не говорить, это подло по отношению к другим :D
У нас прямо форум подлецов... 90% знают и не говорят... :)
Там три строчки кода, может вам стоит с этим разобраться?


Название: Re: Прелестно
Отправлено: Bepec от Октябрь 01, 2014, 19:12
Вырванных из контекста :) Мб и стоит, я не знаю. Я вас собственно и спрашиваю, желая узнать :)


Название: Re: Прелестно
Отправлено: Авварон от Октябрь 01, 2014, 22:48
Метод класса это обычная функция, которой первым параметром передаётся адрес размещения объекта (this). Ваша функция ничего ужасного с объектом не делает, вот если вы по адресу писать начнете, тогда начнутся чудеса.

Цитировать
thiscall
Используется в компиляторах C++. Обеспечивает передачу аргументов при вызовах методов класса в объектно ориентированной среде. Аргументы передаются через стек, справа налево. Очистку стека производит вызываемая функция, то есть тот же самый stdcall. Указатель (this) на объект, для которого вызывается метод, записывается в регистр ECX.
© Вика


Название: Re: Прелестно
Отправлено: Old от Октябрь 01, 2014, 22:51
А там не сказано что делать, если у процессора нет регистра ecx? :)


Название: Re: Прелестно
Отправлено: Авварон от Октябрь 01, 2014, 22:54
Я думаю, регистры есть примерно во всех архитектурах. Поинт в том, что врядли this передаётся через стэк - это медленно.


Название: Re: Прелестно
Отправлено: Авварон от Октябрь 01, 2014, 22:56
ЗЫ: вам, кстати, должно быть известно, что архитектур, кроме х86, не существует http://msdn.microsoft.com/en-us/library/ek8tkfbw(v=vs.71).aspx :)


Название: Re: Прелестно
Отправлено: Old от Октябрь 02, 2014, 06:28
Я думаю, регистры есть примерно во всех архитектурах. Поинт в том, что врядли this передаётся через стэк - это медленно.
Да не принципиально как он передаётся (это все отдается на откуп abi конкретной платформы), главное что он передаётся в функцию в качестве параметра.
Я простым языком хотел объяснить, что метод это обычная функция с дополнительным параметром. :)

P.S. Я очень часто пишу на форум с телефона, поэтому подробно расписывать у меня терпения не хватает. :)


Название: Re: Прелестно
Отправлено: gil9red от Октябрь 02, 2014, 06:51
Я думаю, регистры есть примерно во всех архитектурах. Поинт в том, что врядли this передаётся через стэк - это медленно.
Да не принципиально как он передаётся (это все отдается на откуп abi конкретной платформы), главное что он передаётся в функцию в качестве параметра.
Я простым языком хотел объяснить, что метод это обычная функция с дополнительным параметром. :)

P.S. Я очень часто пишу на форум с телефона, поэтому подробно расписывать у меня терпения не хватает. :)

Как в python, только не так открыто для программиста :)


Название: Re: Прелестно
Отправлено: GreatSnake от Октябрь 02, 2014, 09:43
Ну так что, кто-нибудь что-либо скажет насчёт этого (http://www.prog.org.ru/index.php?topic=27677.msg201421#msg201421)?


Название: Re: Прелестно
Отправлено: _Bers от Октябрь 02, 2014, 10:00
Я простым языком хотел объяснить, что метод это обычная функция с дополнительным параметром. :)

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

Вы пишите так, словно можно закладываться на работоспособность следующего кода:
http://rextester.com/RSDW27510


Код:
#include <iostream>
using namespace std;

struct Example
{
    // мы можем запустить этот метод не как метод, а как обычную свободную функцию
    void foo(int) { cout<<"Example::foo(int);\n"; }
};

int main()
{
    std::cout << "Hello, world!\n";
    
    typedef
        void(*Free)(Example*, int);
    typedef
        void(Example::*Method)(int);
    
    //для того, что бы запустить метод, как свободную функцию
    //нужно выполнить преобразования метода в свободную функцию
    
    Method m= &Example::foo;
    
    //для этого возьмем адрес функции-члена таким образом,
    //что бы компилятор забыл изначальный тип функции-члена
    //это позволит обойти защиту компилятора при некорректном
    //с точки зрения компилятора преобразовании типов функции-члена к функции
    void* proxy = (void*)&m;


    //теперь извлекаем объект обратно, преобразовывая его тип к типу функции
    Free f = *((Free*)proxy);
    
    Example ex;
    
    
    //теперь, если ваша теория верна, мы можем запустить функцию,
    //передав первым аргументом тот самый "не видимый" параметр this
    //а следом все прочие аргуметы
    f(&ex,10);  
}


Представленный выше код дает представление о том, чем на самом деле является функция-член.
А так же иллюстрирует принципиальную схему действий компилятора.

Однако, несмотря на то, что представленный код работает в большинстве случаев - он является UB.
И компилятор, который мне пришлось обмануть, что бы выполнить каст, не зря запрещает такой каст.

По факту, по месту вызова функции-члена, компилятор должен сгенерировать вызов.
Но каким именно он будет - зависит от его способности к оптимизации.

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

Но при этом компилятор закладывается на то, что программист не делает UB.
А значит он не делает преобразований типов, которые запрещено делать.

А значит можно изменить вызов так, что бы параметры прошли через регистры, или ещё каким то особым образом.


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


Компилятор запрещает данные касты, специально, что бы пользователи не могли закладываться на конкретную стратегию вызова.
Это поведение описывается в стандарте как unspecified behaviour.

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



Название: Re: Прелестно
Отправлено: _Bers от Октябрь 02, 2014, 10:03
Ну так что, кто-нибудь что-либо скажет насчёт этого (http://www.prog.org.ru/index.php?topic=27677.msg201421#msg201421)?

Это UB

Считайте что вам просто повезло, что при одиночном наследовании указатель на vtable всегда лежит в начале объекта и совпали индексы функций в нем.

(примечание: вроде бы не гарантируется что это всегда будет именно так)

Код, который наглядно иллюстрирует возможные проблемы:
http://ideone.com/KPiDmR
http://ideone.com/z0bAgC



Название: Re: Прелестно
Отправлено: Old от Октябрь 02, 2014, 10:05
Вы пишите об этом так, словно компилятор, встретив вызов функции-члена, генерирует код вызова обычной функции, передав в качестве первого аргумента - адрес объекта.
Вовсе нет. :)
Посмотрите на начало обсуждения, я постарался просто объяснить, почему код ТС не падает.
Я не собирался вдаваться в подробные объяснения всех механизмов для всех возможных ABI. Это я оставил для самостоятельного изучения ТС.


Название: Re: Прелестно
Отправлено: OKTA от Октябрь 02, 2014, 12:54
Что-то я читал, читал твой пример, _Bers и так и не понял, что ты хотел им показать. Я так понял, что как раз фишку, что передается адрес объекта?  ???


Название: Re: Прелестно
Отправлено: OKTA от Октябрь 02, 2014, 21:31
up или че там говорят обычно?  ;D


Название: Re: Прелестно
Отправлено: Igors от Октябрь 05, 2014, 11:04
Ну так что, кто-нибудь что-либо скажет насчёт этого (http://www.prog.org.ru/index.php?topic=27677.msg201421#msg201421)?
Единственное что могу сказать - сам делал практически так же  :)

Др пример
Код:
ARGB CalcColor( void );

Это не работает если тело CalcColor в С (не cpp) файле. Когда ф-ция вызывается из cpp, она рассчитывает на
Код
C++ (Qt)
void CalcColor( ARGB & c );
и подает "с" как аргумент. Правда варнинг есть, что-то типа "inconsistent linkage" (точно не помню)

Непонятно удивление на 4 страницах :) Самые начальные познания в ассемблере снимают все вопросы


Название: Re: Прелестно
Отправлено: Bepec от Октябрь 05, 2014, 12:17
Тупо не преподавали этот материал, а на практике он не пригождается.


Название: Re: Прелестно
Отправлено: Igors от Октябрь 05, 2014, 13:36
Тупо не преподавали этот материал, а на практике он не пригождается.
Если человек не представляет во что это выливается в командах - ему на уши можно повесить любую лапшу (что нередко и делают на хабре, да и местные знатоки..)

Что-то я читал, читал твой пример, _Bers и так и не понял, что ты хотел им показать. Я так понял, что как раз фишку, что передается адрес объекта?  ???
Упрощенный пример
Код
C++ (Qt)
int DoSomething( float p0, float p1, int num );
Полагать что действительно на стек будет подаваться num, затем p1 и p2 - ну так думают тыкающие мальчики :) Реально может быть что угодно, напр

num в регистре ecx
p0 и p1 в регистрах FP2 и FP3

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


Название: Re: Прелестно
Отправлено: Old от Октябрь 05, 2014, 14:23
Для того, что бы таких заморочек не было, давным давно были приняты соглашения о вызовах с соответствующими директивами и все это расписывается в abi конкретной платформы.


Название: Re: Прелестно
Отправлено: Bepec от Октябрь 05, 2014, 15:07
Замер производительности команд/функций вполне заменяет знание ассемблера, увы.