Здравствуйте VVV, Вы писали:
VVV>Если посмотреть с другой стороны, то любой вызов C++ функции можно описать в терминам C функции, т.е. это будет функция, где первым параметром передаётся указатель на какую-то структуру данных (this). Посему, при вызове такой функции из C этот параметр (this) можно задать каким угодно (и NULL в том числе).
В том то и дело, что тут есть одна существенная тонкость, из-за которой вызов метода объекта в С++ нельзя так запросто описать в терминах C функций.
Пусть от класса A и класса B множественно унаследован класс C:
struct B { int x; };
struct C : B, A {};
Рассмотрим вот такой вызов метода 'foo' через null-указатель типа 'C*':
C* p = NULL;
p->foo(5); // (1)
Как будет выглядеть соответствующий вызов С функции 'foo'? Он будет выглядеть вот так:
foo(p, 5); // (2)
Эквивалентны ли эти вызовы (закроем глаза на неопределенное поведение в вызове (1))? Нет, не эквивалентны. При компиляции, например, MSVC++, эти вызовы распечатают разные результаты на экране. Почему так происходит?
Происходит это потому, что в вызове (2) компилятор вынужден сделать стандартную pointer conversion — конверсию типа первого параметра от типа 'C*' к типу 'A*'. При этом, согласно спецификации языка С++, null-указатель одного типа должен быть переведен в null-указатель другого типа. По этой причине в результате вызова (2) внутрь функции 'foo' будет передан null-указатель и неравенство 'this_ != NULL' не выполнится.
А в вызове (1) ситуация совершенно иная. Для того, чтобы взывать метод класса A через указатель типа 'C*', этот указатель тоже необходимо определенным образом "преобразовать". То, что происходит с указателем в таком случае, не является никакой "официальной" конверсией языка С++. Компилятор обязан просто как-то "сдвинуть" указатель типа 'C*' так, чтобы он стал указывать на экземпляр класса A внутри экземпляра класса C. Как компиялтор будет это делать — детали реализации. Никаких требований о том, чтобы null-указатель одного типа при этом первращался именно в null-указатель другого типа, нет и никогда не было. Эти требования не нужны по той простой причине, что стандарт С++ целиком и полностью запрещает такие вызовы. Ни один уважающий себя компилятор не будет тратить время на проверку равенства указателя NULL. Компилятор просто сдвинет указатель на определенное количесво байт. При этом null-указатель перестанет быть null-указателем. По этой самой причине в результате вызова (1) внутри метода 'A::foo' указатель this не будет null-указателем и неравенство 'this_ != NULL' выполнится.
Вот если бы мы сами заставили компилятор выполнить полноценную конверсию указателя при вызове метода
((A*) p)->foo(5); // (3)
тогда результаты выполнения вызовов (2) и (3) совпали бы. Но пользоваться в программах именно такими вызовами только ради того, чтобы сохранить "правильность" преобразования null-указателей, никто, разумеется, не будет. К тому же это потенциальный удар по производительности.
Отсюда мораль: с ситауациях с множественным наследованием (а согласно стандарту языка — в ситуациях с любым наследованием) не надо думать о вызовах методов классов, как о вызовах обычных С функций с дополнительным параметром. Это несколько разные вещи.
Здравствуйте DarkGray, Вы писали:
DG>Написано ли где-нибудь в стандарте, что компилятор не должен закладываться на то, что this всегда не равен нулю?
Может написано, может не написано. Это, в принципе, неважно. Стандарт вместо этого совершенно глухо запрещает все возможные пути падания внутрь метода объекта через null-указатель. А именно: запрещается разадресовывать null-указатель. Это автоматически означает, что, пока поведение программы определено, внутри метода объекта this никак не может быть равен null-указателю.
DG>А вот для обычных функций не понятно. Могу ли я в качестве this при вызове использовать произвольное значение (в частности NULL) или не могу? То есть будет это приводить к падению или не будет?
Будет приводить к неопределенному поведению. А будет падать или нет — это уже дело десятое. Твоя попытка защититься от такой ситуации путем сравнения 'this' с 'NULL' в рамках языка C++ ситуацию никак не спасает, т.к. у этому моменту ее уже поздно спасать.
Даже если некоторый компилятор все таки делает "корректный" вызов метода через null-указатель, все равно проверка 'this' на NULL в общем случае работать не будет. Т.к., например, нигде не гарантируется, что указатель 'this' базового класса совпадает с указателем 'this' класса-наследника. Даже при одиночном наследовании. А при множественном наследовании твоя проверка в принципе не способна отловить NULL. Посмотри что получится если сделать так:
struct SX { int x; };
struct SB : SX, SA {};
((SB*) 0)->Func();
struct SA
{
void Func() //невиртуальная
{
if (this == NULL)
return;
}
static void StaticFunc() {}
};
void main()
{
((SA*)0)->StaticFunc(); //ИМХО, тут должно быть все нормально
((SA*)0)->Func(); //а вот здесь не понятно, мало ли чего может компилятор напихать
}
DG>Написано ли где-нибудь в стандарте, что компилятор не должен закладываться на то, что this всегда не равен нулю?
АТ>>Может написано, может не написано. Это, в принципе, неважно. Стандарт вместо этого совершенно глухо запрещает все возможные пути попадания внутрь метода объекта через null-указатель. А именно: запрещается разадресовывать null-указатель. Это автоматически означает, что, пока поведение программы определено, внутри метода объекта this никак не может быть равен null-указателю.
ПК>AFAIK, вызов метода сам по себе не подразумевает разыменования указателя. Поэтому в стандарте (9.3.1/1) явно указано, что "If a nonstatic member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined". Т.к. null-pointer гарантированно не является указателем ни на какой объект, это предложение включает и вызов метода через null-pointer.
Я не согласен. Возможность вызова метода класса (причем как статического, так и нестатического) через null-указатель перекрыта стандартом еще раньше. Стандарт языка подчеркивает, что выражение, стоящее слева от '.' или '->' при доступе к члену класса (методу или полю), обязательно вычисляется. Причем оно вычисляется независимо от того, является ли доступаемый член класса статическим или нет. Вычисление этого выражения автоматически означает разыменование использованного в нем указателя. Разыменование null-указателя порождает неопределенное поведение.
Из приведенной тобой выдержки, кстати, может показаться, что к статическим членам класса можно доступаться через null-указатель. Это не так. Любое вычисляемое выражение, содержащее разыменование null-указателя порождает неопределенное поведение. А вот например параметр 'typeid' может содержать разыменованный null-указатель, т.к. выражение-параметр 'typeid' не вычисляется.
Также можно заметить, что если бы вызов метода не подразумевал разыменования указателя, то стандарт языка С++ должен был бы содержать отдельное замечание по поводу вызова виртуальных методов. Эти последние вызывать через null-указатель все таки нельзя (мы все в глубине души знаем, что такой вызов существенно подразумевает разыменование указателя). Я не нашел в стандарте такого замечания.
Здравствуйте Андрей Тарасевич, Вы писали:
АТ>Здравствуйте Alex Fedotov, Вы писали:
AF>>Здравствуйте Андрей Тарасевич, Вы писали:
АТ>>>Для того, чтобы "легализовать" такую реализацию 'GetSafeHwnd' MFC должно либо явно запрещать использование класса 'CWnd' в качестве базового класса в иерархии с множественным наследованием, либо запрещать вызов 'GetSafeHwnd' для экземпляров класса, имеющих множественных предков, либо еще как-то ограничить использование 'GetSafeHwnd'. Вполне может быть, что такие ограничения существуют. Я, правда, о них пока не слышал.
AF>>Таки есть ограничения: AF>>TN016: Using C++ Multiple Inheritance with MFC
АТ>Это, в некотором смысле, оправдывает вышеприведенный вариант 'GetSafeHwnd', как деталь реализации платформеннозависимой библиотеки.
MFC платформеннонезависимой никогда не планировалась. Она использует не только MS-specific код, но и ассемблерные вставки. Что касается MI, то никто не мешает мне "подмешать" свои классы к классам MFC — с точки зрения MFC это абсолютно законно. Но даже если брать два класса, порождённых от CObject, MI возможно, как показывает пример из той же приведённой Алексом статьи:
#include <afxwin.h>
class CHelloAppAndFrame : public CFrameWnd, public CWinApp
{
public:
CHelloAppAndFrame()
{ }
// Necessary evil for MI disambiguityvoid* operator new(size_t nSize)
{ return CFrameWnd::operator new(nSize); }
void operator delete(void* p)
{ CFrameWnd::operator delete(p); }
// Implementation
// CWinApp overridesvirtual BOOL InitInstance();
// CFrameWnd overridesvirtual void PostNcDestroy();
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CHelloAppAndFrame, CFrameWnd)
ON_WM_PAINT()
END_MESSAGE_MAP()
// since the frame window is not allocated on the heap, we must
// override PostNCDestroy not to delete the frame objectvoid CHelloAppAndFrame::PostNcDestroy()
{
// do nothing (do not call base class)
}
void CHelloAppAndFrame::OnPaint()
{
CPaintDC dc(this);
CRect rect;
GetClientRect(rect);
CString s = "Hello, Windows!";
dc.SetTextAlign(TA_BASELINE | TA_CENTER);
dc.SetTextColor(::GetSysColor(COLOR_WINDOWTEXT));
dc.SetBkMode(TRANSPARENT);
dc.TextOut(rect.right / 2, rect.bottom / 2, s);
}
// Application initialization
BOOL CHelloAppAndFrame::InitInstance()
{
// first create the main frameif (!CFrameWnd::Create(NULL, "Multiple Inheritance Sample",
WS_OVERLAPPEDWINDOW, rectDefault))
return FALSE;
// the application object is also a frame window
m_pMainWnd = this;
ShowWindow(m_nCmdShow);
return TRUE;
}
CHelloAppAndFrame theHelloAppAndFrame;
--
Я думал, ты огромный страшный Бажище,
А ты недоучка, крохотный Бажик...
Здравствуйте IT, Вы писали:
VVV>>т.е. во втором случае идёт сравнение с 0 и если не 0, то только тогда компилятор приводит тип.
IT>Зря он это делает, может он ещё и проверку выхода за границы массивов мне делать будет
Не зря. Если компилятор не сгенерирует код для проверки на null-pointer при преобразовании указателя, то в последующем, при сравнении полученного после преобразования 0 (NULL) указателя с 0 (NULL), результат может быть самым неожиданным.
class A { int i; };
class B { int i; };
class C : public A, public B { };
C* c = 0;
B* b = c; // если здесь не будет проверки на 0,
// на типичной 32-битной реализации значением указателя b
// станет что-нибудь типа 0x00000004
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте DarkGray, Вы писали:
DG>Должен ли по стандарту работать следующий код? DG>
DG>struct SA
DG>{
DG> void Func() //невиртуальная
DG> {
DG> if (this == NULL)
DG> return;
DG> }
DG> static void StaticFunc() {}
DG>};
DG>void main()
DG>{
DG> ((SA*)0)->StaticFunc(); //ИМХО, тут должно быть все нормально
DG> ((SA*)0)->Func(); //а вот здесь не понятно, мало ли чего может компилятор напихать
DG>}
DG>
Нет, не должен. По стандарту, применение оператора '->' к null-указателю, вызывает undefined behavior.
Здравствуйте DarkGray, Вы писали:
DG>Здравствуйте IT, Вы писали:
IT>>Здравствуйте DarkGray, Вы писали:
DG>>>Должен ли по стандарту работать следующий код?
IT>>Давай перепишем и посмотрим причём тут стандарт:
IT>>
IT>>struct SA
IT>>{
IT>> void Func(); //невиртуальная и не важно
DG>//как раз важно что функция не виртуальная, для виртуальной функции все будет падать со свистом при нулевом указателе
IT>> static void StaticFunc();
IT>>};
IT>>void f(SA*)
IT>>{
IT>> SA->Func(); // откуда компилятору знать, что у программера на уме
IT>>}
IT>>void main()
IT>>{
IT>> SA::StaticFunc(); // можно обойтись и без объекта
IT>> f((SA*)0); // мало ли чего можно компилятору подсунуть
IT>>}
IT>>
DG>Наверное, не правильно вопрос задал...
DG>Написано ли где-нибудь в стандарте, что компилятор не должен закладываться на то, что this всегда не равен нулю?
DG>Для виртуальный функций — понятно. Компилятор не явно закладывается на то, что this не нуль и этот указатель использует в своих целях, по нему ищет таблицу виртуальных функции. DG>В случае нулевого указателя все падает.
DG>Для статик функции то же — понятно, так как this-а у них совсем нет.
DG>А вот для обычных функций не понятно. Могу ли я в качестве this при вызове использовать произвольное значение (в частности NULL) или не могу? То есть будет это приводить к падению или не будет?
Если посмотреть с другой стороны, то любой вызов C++ функции можно описать в терминам C функции, т.е. это будет функция, где первым параметром передаётся указатель на какую-то структуру даннык (this). Посему, при вызове такой функции из C этот параметр (this) можно задать каким угодно (и NULL в том числе). Например, если посмотреть МАПИшные примеры, то там все указатели проверяются на валидность, например так:
/* BAD_STANDARD_OBJ
*
* This macro insures that the object is a writable object of the correct size
* and that this method belongs to the object.
*
* NOTES ON USE!
* This depends upon using the standard method of declaring the object
* interface.
*
* prefix is the method prefix you chose when declaring the object interface.
* method is the standard method name of the calling method.
* lpVtbl is the name of the lpVtbl element of your object.
*/#define BAD_STANDARD_OBJ( lpObj, prefix, method, lpVtbl) ( IsBadWritePtr( (lpObj), sizeof(*lpObj)) || IsBadReadPtr( (void *) &(lpObj->lpVtbl->method), sizeof(LPVOID)) ||( ( LPVOID) (lpObj->lpVtbl->method) != (LPVOID) (prefix##method)))
#define FBadUnknown( lpObj ) ( IsBadReadPtr( (lpObj), sizeof(LPVOID) ) || IsBadReadPtr( (lpObj)->lpVtbl, 3 * sizeof(LPUNKNOWN) ) || IsBadCodePtr( (FARPROC)(lpObj)->lpVtbl->QueryInterface ))
Здравствуйте Alexander Shargin, Вы писали:
AF>>Как Андрей уже сказал, вызов метода с недействительным значением this приводит к undefined behavior. Это, однако, не останавливает разработчиков некоторых библиотек он написания вот такого кода:
AF>>
AS>От себя добавлю, что при реализации стандартной библиотеки также иногда прибегают к разименованию нулевого указателя. Например, так реализован макрос offsetof, который интенсивно используется как в MFC, так и в ATL:
AS>
Реализация стандартной библиотеки имеет право опираться на любые implementation defined особенности платформы, в том числе на определенность "неопределенного поведения". При этом, разумеется, требуется, чтобы поведение оставалось определенным для всех легальных применений данной конструкции. Для вышеприведенной реализации 'offsetof' это условие выполняется. Для 'GetSafeHwnd' — нет.
Для того, чтобы "легализовать" такую реализацию 'GetSafeHwnd' MFC должно либо явно запрещать использование класса 'CWnd' в качестве базового класса в иерархии с множественным наследованием, либо запрещать вызов 'GetSafeHwnd' для экземпляров класса, имеющих множественных предков, либо еще как-то ограничить использование 'GetSafeHwnd'. Вполне может быть, что такие ограничения существуют. Я, правда, о них пока не слышал.
Здравствуйте DarkGray, Вы писали:
DG>А вот для обычных функций не понятно. Могу ли я в качестве this при вызове использовать произвольное значение (в частности NULL) или не могу? То есть будет это приводить к падению или не будет?
Как Андрей уже сказал, вызов метода с недействительным значением this приводит к undefined behavior. Это, однако, не останавливает разработчиков некоторых библиотек он написания вот такого кода:
SS>Здесь вроде придратся не к чему (даже плохим стилем не обзовешь), но это абсолютно то же самое что и в первом случае.
Нет, это не абсолютно то же самое. Теперь, чтобы вызвать эту функцию для указателя на класс, унаследованный от 'CWnd', компилятору придется выполнить конверсию типа указателя. При выполнении конверсии гарантируется, что null-указатель одного типа будет сконвертирован именно в null-указатель другого типа. Независимо от типа наследования — одиночное или множественное. Поэтому такой вариант с внешней функцией гарантированно будет работать всегда. А работоспособность исходного варианта с функцией-членом ничем не гарантирована. В случае множественного наследования исходный вариант просто сядет в лужу.
SS>А в случае множественного наследования проблема с null указателями есть не только у функций членов. Просто не верно утверждение "если производный класс null то и базовые классы null" и это прийдется учитывать всегда.
Если проверка на NULL делается во внешней функции, то никаких проблем с множественным наследованием нет. Даже задумываться об этом не надо.
Здравствуйте Андрей Тарасевич, Вы писали:
DG>>Написано ли где-нибудь в стандарте, что компилятор не должен закладываться на то, что this всегда не равен нулю?
АТ>Может написано, может не написано. Это, в принципе, неважно. Стандарт вместо этого совершенно глухо запрещает все возможные пути падания внутрь метода объекта через null-указатель. А именно: запрещается разадресовывать null-указатель. Это автоматически означает, что, пока поведение программы определено, внутри метода объекта this никак не может быть равен null-указателю.
AFAIK, вызов метода сам по себе не подразумевает разыменования указателя. Поэтому в стандарте (9.3.1/1) явно указано, что "If a nonstatic member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined". Т.к. null-pointer гарантированно не является указателем ни на какой объект, это предложение включает и вызов метода через null-pointer.
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте Андрей Тарасевич, Вы писали:
АТ>Для того, чтобы "легализовать" такую реализацию 'GetSafeHwnd' MFC должно либо явно запрещать использование класса 'CWnd' в качестве базового класса в иерархии с множественным наследованием, либо запрещать вызов 'GetSafeHwnd' для экземпляров класса, имеющих множественных предков, либо еще как-то ограничить использование 'GetSafeHwnd'. Вполне может быть, что такие ограничения существуют. Я, правда, о них пока не слышал.
Таки есть ограничения:
TN016: Using C++ Multiple Inheritance with MFC
AS>MFC платформеннонезависимой никогда не планировалась.
Неправда ваша Если мне окончательно не отшибло память, то та версия MFC, что шла с VC 4.0, существовала не только для Win32, но и для Mac OS.
AS>Она использует не только MS-specific код, но и ассемблерные вставки.
Ну дык реализация библиотеки для конкретной платформы могет делать что ей заблагорассудится. Сишная RTL от MS тоже кое-где на ассемблере писана, главное чтоб интерфейс был одинаковымю
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
struct SA
{
void Func() //невиртуальная
{
if (this == NULL)
return;
}
static void StaticFunc() {}
};
void main()
{
((SA*)0)->StaticFunc(); //ИМХО, тут должно быть все нормально
((SA*)0)->Func(); //а вот здесь не понятно, мало ли чего может компилятор напихать
}
Здравствуйте DarkGray, Вы писали:
DG>Должен ли по стандарту работать следующий код?
Давай перепишем и посмотрим причём тут стандарт:
struct SA
{
void Func(); //невиртуальная и не важноstatic void StaticFunc();
};
void f(SA*)
{
SA->Func(); // откуда компилятору знать, что у программера на уме
}
void main()
{
SA::StaticFunc(); // можно обойтись и без объекта
f((SA*)0); // мало ли чего можно компилятору подсунуть
}
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте IT, Вы писали:
IT>Здравствуйте DarkGray, Вы писали:
DG>>Должен ли по стандарту работать следующий код?
IT>Давай перепишем и посмотрим причём тут стандарт:
IT>
IT>struct SA
IT>{
IT> void Func(); //невиртуальная и не важно
//как раз важно что функция не виртуальная, для виртуальной функции все будет падать со свистом при нулевом указателе
IT> static void StaticFunc();
IT>};
IT>void f(SA*)
IT>{
IT> SA->Func(); // откуда компилятору знать, что у программера на уме
IT>}
IT>void main()
IT>{
IT> SA::StaticFunc(); // можно обойтись и без объекта
IT> f((SA*)0); // мало ли чего можно компилятору подсунуть
IT>}
IT>
Наверное, не правильно вопрос задал...
Написано ли где-нибудь в стандарте, что компилятор не должен закладываться на то, что this всегда не равен нулю?
Для виртуальный функций — понятно. Компилятор не явно закладывается на то, что this не нуль и этот указатель использует в своих целях, по нему ищет таблицу виртуальных функции.
В случае нулевого указателя все падает.
Для статик функции то же — понятно, так как this-а у них совсем нет.
А вот для обычных функций не понятно. Могу ли я в качестве this при вызове использовать произвольное значение (в частности NULL) или не могу? То есть будет это приводить к падению или не будет?
Здравствуйте DarkGray, Вы писали:
DG>А вот для обычных функций не понятно. Могу ли я в качестве this при вызове использовать произвольное значение (в частности NULL) или не могу? То есть будет это приводить к падению или не будет?
Может быть, если твой метод имеет доступ к членам класса или вызывает другие методы, которые это делают. И точно будет, если твой метод вызывает виртуальные функции.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте Alex Fedotov, Вы писали:
AF>Как Андрей уже сказал, вызов метода с недействительным значением this приводит к undefined behavior. Это, однако, не останавливает разработчиков некоторых библиотек он написания вот такого кода:
AF>
А как бы им надо было написать чтобы реализовать такую функциональность и соответствовать стандарту(ну или не использовать плохой стиль программирования)
Например объявить функцию за пределами класса:
Здесь вроде придратся не к чему (даже плохим стилем не обзовешь), но это абсолютно то же самое что и в первом случае.
А в случае множественного наследования проблема с null указателями есть не только у функций членов. Просто не верно утверждение "если производный класс null то и базовые классы null" и это прийдется учитывать всегда.
И что плохого в том, что в не virtual функции используется this==null, в конце концов после компиляции в obj файле она будет лежать как обычная функция, у которой первый параметр сравнивается с 0.
Здравствуйте Silver_s, Вы писали:
SS>А в случае множественного наследования проблема с null указателями есть не только у функций членов. Просто не верно утверждение "если производный класс null то и базовые классы null" и это прийдется учитывать всегда. SS>И что плохого в том, что в не virtual функции используется this==null, в конце концов после компиляции в obj файле она будет лежать как обычная функция, у которой первый параметр сравнивается с 0.
Короче, по стандарту не написано, поэтому все компиляторы наплюют на нуллевой this (а могли бы, могли бы! Например, считать, что NULL-потомок имеет всех NULL-предков).
Непонятно, правда — как реагировать тогда, когда нужно разыменовать NULL.
(BYTE*)(NULL)[0] = 1; // исключение защиты памяти
(CObject)(NULL)->VirtualFn(); // в принципе, то же, но — не факт, не факт...
Здравствуйте Alex Fedotov, Вы писали:
AF>Как Андрей уже сказал, вызов метода с недействительным значением this приводит к undefined behavior. Это, однако, не останавливает разработчиков некоторых библиотек он написания вот такого кода:
AF>
От себя добавлю, что при реализации стандартной библиотеки также иногда прибегают к разименованию нулевого указателя. Например, так реализован макрос offsetof, который интенсивно используется как в MFC, так и в ATL:
#define offsetof(s,m) (size_t)&(((s *)0)->m)
--
Я думал, ты огромный страшный Бажище,
А ты недоучка, крохотный Бажик...
Здравствуйте Alex Fedotov, Вы писали:
AF>Здравствуйте Андрей Тарасевич, Вы писали:
АТ>>Для того, чтобы "легализовать" такую реализацию 'GetSafeHwnd' MFC должно либо явно запрещать использование класса 'CWnd' в качестве базового класса в иерархии с множественным наследованием, либо запрещать вызов 'GetSafeHwnd' для экземпляров класса, имеющих множественных предков, либо еще как-то ограничить использование 'GetSafeHwnd'. Вполне может быть, что такие ограничения существуют. Я, правда, о них пока не слышал.
AF>Таки есть ограничения: AF>TN016: Using C++ Multiple Inheritance with MFC
Это, в некотором смысле, оправдывает вышеприведенный вариант 'GetSafeHwnd', как деталь реализации платформеннозависимой библиотеки.
Здравствуйте Alexander Shargin, Вы писали:
AF>>>Таки есть ограничения: AF>>>TN016: Using C++ Multiple Inheritance with MFC
АТ>>Это, в некотором смысле, оправдывает вышеприведенный вариант 'GetSafeHwnd', как деталь реализации платформеннозависимой библиотеки.
AS>MFC платформеннонезависимой никогда не планировалась. Она использует не только MS-specific код, но и ассемблерные вставки.
Я так и написал: "...как деталь реализации платформеннозависимой библиотеки..."
AS>Что касается MI, то никто не мешает мне "подмешать" свои классы к классам MFC — с точки зрения MFC это абсолютно законно.
Да, законно. При выполненияи того условия, что 'CWnd' будет первой базой любого унаследованного класса, как сказано в статье.
Здравствуйте Андрей Тарасевич, Вы писали:
АТ>Здравствуйте VVV, Вы писали:
VVV>>Если посмотреть с другой стороны, то любой вызов C++ функции можно описать в терминам C функции, т.е. это будет функция, где первым параметром передаётся указатель на какую-то структуру даннык (this). Посему, при вызове такой функции из C этот параметр (this) можно задать каким угодно (и NULL в том числе).
АТ>В том то и дело, что тут есть одна существенная тонкость, из-за которой вызов метода объекта в С++ нельзя так запросто описать в терминах C функций.
АТ>Предположим, что у нас имеется вот такой класс A
АТ>
АТ>Пусть от класса A и класса B множественно унаследован класс C:
АТ>
АТ>struct B { int x; };
АТ>struct C : B, A {};
АТ>
АТ>Рассмотрим вот такой вызов метода 'foo' через null-указатель типа 'C*':
АТ>
АТ>C* p = NULL;
p->>foo(5); // (1)
АТ>
АТ>Как будет выглядеть соответствующий вызов С функции 'foo'? Он будет выглядеть вот так:
АТ>
АТ>foo(p, 5); // (2)
АТ>
АТ>Эквивалентны ли эти вызовы (закроем глаза на неопределенное поведение в вызове (1))? Нет, не эквивалентны. При компиляции, например, MSVC++, эти вызовы распечатают разные результаты на экране. Почему так происходит?
АТ>Происходит это потому, что в вызове (2) компилятор вынужден сделать стандартную pointer conversion — конверсию типа первого параметра от типа 'C*' к типу 'A*'. При этом, согласно спецификации языка С++, null-указатель одного типа должен быть переведен в null-указатель другого типа. По этой причине в результате вызова (2) внутрь функции 'foo' будет передан null-указатель и неравенство 'this_ != NULL' не выполнится.
АТ>А в вызове (1) ситуация совершенно иная. Для того, чтобы взывать метод класса A через указатель типа 'C*', этот указатель тоже необходимо определенным образом "преобразовать". То, что происходит с указателем в таком случае, не является никакой "официальной" конверсией языка С++. Компилятор обязан просто как-то "сдвинуть" указатель типа 'C*' так, чтобы он стал указывать на экземпляр класса A внутри экземпляра класса C. Как компиялтор будет это делать — детали реализации. Никаких требований о том, чтобы null-указатель одного типа при этом первращался именно в null-указатель другого типа, нет и никогда не было. Эти требования не нужны по той простой причине, что стандарт С++ целиком и полностью запрещает такие вызовы. Ни один уважающий себя компилятор не будет тратить время на проверку равенства указателя NULL. Компилятор просто сдвинет указатель на определенное количесво байт. При этом null-указатель перестанет быть null-указателем. По этой самой причине в результате вызова (1) внутри метода 'A::foo' указатель this не будет null-указателем и неравенство 'this_ != NULL' выполнится.
АТ>Вот если бы мы сами заставили компилятор выполнить полноценную конверсию указателя при вызове метода
АТ>
АТ>((A*) p)->foo(5); // (3)
АТ>
АТ>тогда результаты выполнения вызовов (2) и (3) совпали бы. Но пользоваться в программах именно такими вызовами только ради того, чтобы сохранить "правильность" преобразования null-указателей, никто, разумеется, не будет. К тому же это потенциальный удар по производительности.
АТ>Отсюда мораль: с ситауациях с множественным наследованием (а согласно стандарту языка — в ситуациях с любым наследованием) не надо думать о вызовах методов классов, как о вызовах обычных С функций с дополнительным параметром. Это несколько разные вещи.
Я и не говорил, что это правильно или так надо делать или при этом не будет проблем , но вопрос был такой:
...
А вот для обычных функций не понятно. Могу ли я в качестве this при вызове использовать произвольное значение (в частности NULL) или не могу? То есть будет это приводить к падению или не будет?
и я показал, как в качестве this передать произвольное значение и не более того.
опять же привёл пример как проверять указатели на валидность через IsBadReadPtr...
Здравствуйте Андрей Тарасевич, Вы писали:
АТ>Здравствуйте VVV, Вы писали:
VVV>>Если посмотреть с другой стороны, то любой вызов C++ функции можно описать в терминам C функции, т.е. это будет функция, где первым параметром передаётся указатель на какую-то структуру даннык (this). Посему, при вызове такой функции из C этот параметр (this) можно задать каким угодно (и NULL в том числе).
АТ>В том то и дело, что тут есть одна существенная тонкость, из-за которой вызов метода объекта в С++ нельзя так запросто описать в терминах C функций.
АТ>Предположим, что у нас имеется вот такой класс A
АТ>
АТ>Пусть от класса A и класса B множественно унаследован класс C:
АТ>
АТ>struct B { int x; };
АТ>struct C : B, A {};
АТ>
АТ>Рассмотрим вот такой вызов метода 'foo' через null-указатель типа 'C*':
АТ>
АТ>C* p = NULL;
p->>foo(5); // (1)
АТ>
АТ>Как будет выглядеть соответствующий вызов С функции 'foo'? Он будет выглядеть вот так:
АТ>
АТ>foo(p, 5); // (2)
АТ>
АТ>Эквивалентны ли эти вызовы (закроем глаза на неопределенное поведение в вызове (1))? Нет, не эквивалентны. При компиляции, например, MSVC++, эти вызовы распечатают разные результаты на экране. Почему так происходит?
АТ>Происходит это потому, что в вызове (2) компилятор вынужден сделать стандартную pointer conversion — конверсию типа первого параметра от типа 'C*' к типу 'A*'. При этом, согласно спецификации языка С++, null-указатель одного типа должен быть переведен в null-указатель другого типа. По этой причине в результате вызова (2) внутрь функции 'foo' будет передан null-указатель и неравенство 'this_ != NULL' не выполнится.
АТ>А в вызове (1) ситуация совершенно иная. Для того, чтобы взывать метод класса A через указатель типа 'C*', этот указатель тоже необходимо определенным образом "преобразовать". То, что происходит с указателем в таком случае, не является никакой "официальной" конверсией языка С++. Компилятор обязан просто как-то "сдвинуть" указатель типа 'C*' так, чтобы он стал указывать на экземпляр класса A внутри экземпляра класса C. Как компиялтор будет это делать — детали реализации. Никаких требований о том, чтобы null-указатель одного типа при этом первращался именно в null-указатель другого типа, нет и никогда не было. Эти требования не нужны по той простой причине, что стандарт С++ целиком и полностью запрещает такие вызовы. Ни один уважающий себя компилятор не будет тратить время на проверку равенства указателя NULL. Компилятор просто сдвинет указатель на определенное количесво байт. При этом null-указатель перестанет быть null-указателем. По этой самой причине в результате вызова (1) внутри метода 'A::foo' указатель this не будет null-указателем и неравенство 'this_ != NULL' выполнится.
АТ>Вот если бы мы сами заставили компилятор выполнить полноценную конверсию указателя при вызове метода
АТ>
АТ>((A*) p)->foo(5); // (3)
АТ>
АТ>тогда результаты выполнения вызовов (2) и (3) совпали бы. Но пользоваться в программах именно такими вызовами только ради того, чтобы сохранить "правильность" преобразования null-указателей, никто, разумеется, не будет. К тому же это потенциальный удар по производительности.
АТ>Отсюда мораль: с ситауациях с множественным наследованием (а согласно стандарту языка — в ситуациях с любым наследованием) не надо думать о вызовах методов классов, как о вызовах обычных С функций с дополнительным параметром. Это несколько разные вещи.
не ради разведения полемики, а ради истины
#include"stdafx.h"#include <iostream.h>
struct A{
void foo(int i)
{
cout << hex << this << " " << i << endl;
}
};
void foo(A* this_, int i)
{
cout << hex << this_ << " " << i << endl;
}
struct B{
int x;
};
struct C: B, A{
};
int main(int argc, char* argv[])
{
C *p=(C*)1;
p->foo(5);
foo(p, 5);
return 0;
}
оказалось, что как раз VC 6.0 в случае foo(p, 5); проверяет p на 0 и не делает смещения в 4 байта, только если p равен NULL, в приведённом же примере где C *p=(C*)1 this и this_ совпадают.
Здравствуйте Андрей Тарасевич, Вы писали:
АТ>Здравствуйте VVV, Вы писали:
... АТ>В том то и дело, что тут есть одна существенная тонкость, из-за которой вызов метода объекта в С++ нельзя так запросто описать в терминах C функций.
... АТ>Отсюда мораль: с ситауациях с множественным наследованием (а согласно стандарту языка — в ситуациях с любым наследованием) не надо думать о вызовах методов классов, как о вызовах обычных С функций с дополнительным параметром. Это несколько разные вещи.
Неверно. Все COM интерфейсы описаны и как C++ классы и как структуры на C и вызовы из C с передачей THIS первым параметром очень даже хорошо работают, хотя мы знаем, что реализуются COM объекты в большинстве случаев как классы с множественным наследованием.
Проблема, приведённая Вами проявляется только на указателе == NULL, потому что компилятор пытается оставить NULL-указатель инвариантом и не делает ему приведение типов, во всех остальных случаях С вызовы с первым this работают так же как и C++ (иначе как бы тогда работал COM???).
Здравствуйте VVV, Вы писали:
АТ>>Отсюда мораль: с ситауациях с множественным наследованием (а согласно стандарту языка — в ситуациях с любым наследованием) не надо думать о вызовах методов классов, как о вызовах обычных С функций с дополнительным параметром. Это несколько разные вещи.
VVV>Неверно. Все COM интерфейсы описаны и как C++ классы и как структуры на C и вызовы из C с передачей THIS первым параметром очень даже хорошо работают, хотя мы знаем, что реализуются COM объекты в большинстве случаев как классы с множественным наследованием.
Ты не совсем внимательно читал поедыдущий постинг. Множественное наследование интерфейсов (я позволю себе ввести такой термин) и наследование регулярных классов C++ немного отличаются. Интерфейсы можно рассматривать как упрощённые классы, но упрощенные настолько, что это упрощение снимает большинство проблем связанных с множественным наследованием. У интерфейсов нет данных, а это самая большая проблема при множественном наследовании и у Андрея в "экземпляр класса A внутри экземпляра класса C" как раз и идёт речь не об указателях на таблицы виртуальных методов, а именно об экземплярах данных этих классов.
Что касается интерфейсов, то они так или иначе поддерживаются уже большинством языков, в том числе вошли как стандарт в .NET и никаких проблем с множественным наследованием у них на возникает. Это просто группа указателей в общей таблице виртуальных методов, не более того.
VVV>Проблема, приведённая Вами проявляется только на указателе == NULL, потому что компилятор пытается оставить NULL-указатель инвариантом и не делает ему приведение типов, во всех остальных случаях С вызовы с первым this работают так же как и C++ (иначе как бы тогда работал COM???).
Компилятору по барабану какое там значение у указателя, NULL или не NULL.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте IT, Вы писали:
IT>Здравствуйте VVV, Вы писали:
АТ>>>Отсюда мораль: с ситауациях с множественным наследованием (а согласно стандарту языка — в ситуациях с любым наследованием) не надо думать о вызовах методов классов, как о вызовах обычных С функций с дополнительным параметром. Это несколько разные вещи.
VVV>>Неверно. Все COM интерфейсы описаны и как C++ классы и как структуры на C и вызовы из C с передачей THIS первым параметром очень даже хорошо работают, хотя мы знаем, что реализуются COM объекты в большинстве случаев как классы с множественным наследованием.
IT>Ты не совсем внимательно читал поедыдущий постинг. Множественное наследование интерфейсов (я позволю себе ввести такой термин) и наследование регулярных классов C++ немного отличаются. Интерфейсы можно рассматривать как упрощённые классы, но упрощенные настолько, что это упрощение снимает большинство проблем связанных с множественным наследованием. У интерфейсов нет данных, а это самая большая проблема при множественном наследовании и у Андрея в "экземпляр класса A внутри экземпляра класса C" как раз и идёт речь не об указателях на таблицы виртуальных методов, а именно об экземплярах данных этих классов.
IT>Что касается интерфейсов, то они так или иначе поддерживаются уже большинством языков, в том числе вошли как стандарт в .NET и никаких проблем с множественным наследованием у них на возникает. Это просто группа указателей в общей таблице виртуальных методов, не более того.
VVV>>Проблема, приведённая Вами проявляется только на указателе == NULL, потому что компилятор пытается оставить NULL-указатель инвариантом и не делает ему приведение типов, во всех остальных случаях С вызовы с первым this работают так же как и C++ (иначе как бы тогда работал COM???).
IT>Компилятору по барабану какое там значение у указателя, NULL или не NULL.
В том то и дело, что не "по барабану"...
из предыдущего примера:
int main(int argc, char* argv[])
{
C *p=(C*)NULL;
p->foo(5);
foo(p, 5);
return 0;
}
т.е. во втором случае идёт сравнение с 0 и если не 0, то только тогда компилятор приводит тип.
насчёт наследования:
class Obj1 : public IUnknown{
virtual int Sum();
int x;
int y;
char buf[128];
};
class Obj2 : public IUnknown{
virtual int Get();
int q;
int w;
char name[256];
};
class MainObj: public IUnknown, public Obj1, public Obj2{
int zzz;
int xxx;
};
разве это не нормальный COM объект? (конечно, наружу я выставлю примерно такое:
class IObj1:public IUnknown{
public:
virtual int Sum()=0;
}
Здравствуйте VVV, Вы писали:
VVV>... АТ>>В том то и дело, что тут есть одна существенная тонкость, из-за которой вызов метода объекта в С++ нельзя так запросто описать в терминах C функций.
VVV>... АТ>>Отсюда мораль: с ситауациях с множественным наследованием (а согласно стандарту языка — в ситуациях с любым наследованием) не надо думать о вызовах методов классов, как о вызовах обычных С функций с дополнительным параметром. Это несколько разные вещи.
VVV>Неверно. Все COM интерфейсы описаны и как C++ классы и как структуры на C и вызовы из C с передачей THIS первым параметром очень даже хорошо работают, хотя мы знаем, что реализуются COM объекты в большинстве случаев как классы с множественным наследованием.
Что значит "неверно"? В-первых, реализация COM-интерфейсов зафиксирована на очень низком уровне (бинараная совместимось), и "подтасована" под организацию таблиц виртуальных методов конкретным компилятором. К языку С++ это никакого отношения не имеет. Мое же утверждение относилось именно к языку С++.
VVV>Проблема, приведённая Вами проявляется только на указателе == NULL, потому что компилятор пытается оставить NULL-указатель инвариантом и не делает ему приведение типов, во всех остальных случаях С вызовы с первым this работают так же как и C++ (иначе как бы тогда работал COM???).
Совершенно верно. Эта проблема проявляется только на null-указателе. Я никогда не говорил, что она будет проявляться на каких-то других указателях. Примера с null совершенно достаточно для того, чтобы сделать то утверждение, которое я сделал. Где тут "неверно"?
И я все таки не понимаю, при чем тут COM. В COM в качестве указателя на интерфейс null-указатель использован быть не может.
Здравствуйте VVV, Вы писали:
VVV>не ради разведения полемики, а ради истины VVV>... VVV>int main(int argc, char* argv[]) VVV>{ VVV> C *p=(C*)1; VVV> p->foo(5);
VVV> foo(p, 5);
VVV> return 0; VVV>} VVV>[/ccode]
VVV>оказалось, что как раз VC 6.0 в случае foo(p, 5); проверяет p на 0 и не делает смещения в 4 байта, только если p равен NULL, в приведённом же примере где C *p=(C*)1 this и this_ совпадают.
Разумеется. Об этом и идет речь в моем предыдущем сообщении. Так что именно ты хочешь этим сказать?
Здравствуйте VVV, Вы писали:
VVV>В том то и дело, что не "по барабану"... VVV>т.е. во втором случае идёт сравнение с 0 и если не 0, то только тогда компилятор приводит тип.
Зря он это делает, может он ещё и проверку выхода за границы массивов мне делать будет
VVV>насчёт наследования:
[skip] VVV> разве это не нормальный COM объект?
Это нормальный C++ класс, реализующий интерфейс IUnknown. COM объектом он станет, когда ты его создашь посредством одной из функций в контексте COM.
VVV>(конечно, наружу я выставлю примерно такое:
Вот-вот, и это правильно, наружу выставляется именно интерфейс.
Хочу ещё раз повториться. Интерфейс — это не полноценный C++ класс. Упрощенно его реализацию можно рассматривать как указатель на последовательную группу виртуальных функций внутри общей таблицы виртуальных методов. При этом никакие данные (поля) класса его не касаются. В языках, где запрещено множественное наследование классов, как правило разрешено множественное наследование интерфейсов, т.к. это легко реализуемо и не вносит в описание класса неразберихи.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте Павел Кузнецов, Вы писали:
ПК>Не зря. Если компилятор не сгенерирует код для проверки на null-pointer при преобразовании указателя, то в последующем, при сравнении полученного после преобразования 0 (NULL) указателя с 0 (NULL), результат может быть самым неожиданным.
Да, ты прав. Я закциклился на первоначальной теме топика
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте Андрей Тарасевич, Вы писали:
АТ>Здравствуйте VVV, Вы писали:
VVV>>оказалось, что как раз VC 6.0 в случае foo(p, 5); проверяет p на 0 и не делает смещения в 4 байта, только если p равен NULL, в приведённом же примере где C *p=(C*)1 this и this_ совпадают.
АТ>Разумеется. Об этом и идет речь в моем предыдущем сообщении. Так что именно ты хочешь этим сказать?
То же, что и в самом начале — что вызовы C++ функций могут быть проэмулированы вызовами C функций с передачей this первым параметром.
Здравствуйте VVV, Вы писали:
АТ>>Разумеется. Об этом и идет речь в моем предыдущем сообщении. Так что именно ты хочешь этим сказать?
VVV>То же, что и в самом начале — что вызовы C++ функций могут быть проэмулированы вызовами C функций с передачей this первым параметром.
В рамках легального С++ — да, может быть проэмулирована. Так что я с тобой полностью согласен — в рамках легального С++.
Но в этой дискуссии речь идет о [нелегальном] вызове метода класса через null-указатель не объект (обрати внимание на поле "тема"). Как только речь идет о вызове метода класса через null-указатель на объект, обычный вызов метода и вышеупомянутая эмуляция дают (могут давать) разные результаты. Я хотел сказать только это и ничего больше.
Здравствуйте Андрей Тарасевич, Вы писали:
АТ>Здравствуйте VVV, Вы писали:
VVV>>... АТ>>>В том то и дело, что тут есть одна существенная тонкость, из-за которой вызов метода объекта в С++ нельзя так запросто описать в терминах C функций.
VVV>>... АТ>>>Отсюда мораль: с ситауациях с множественным наследованием (а согласно стандарту языка — в ситуациях с любым наследованием) не надо думать о вызовах методов классов, как о вызовах обычных С функций с дополнительным параметром. Это несколько разные вещи.
VVV>>Неверно. Все COM интерфейсы описаны и как C++ классы и как структуры на C и вызовы из C с передачей THIS первым параметром очень даже хорошо работают, хотя мы знаем, что реализуются COM объекты в большинстве случаев как классы с множественным наследованием.
АТ>Что значит "неверно"? В-первых, реализация COM-интерфейсов зафиксирована на очень низком уровне (бинараная совместимось), и "подтасована" под организацию таблиц виртуальных методов конкретным компилятором. К языку С++ это никакого отношения не имеет. Мое же утверждение относилось именно к языку С++.
VVV>>Проблема, приведённая Вами проявляется только на указателе == NULL, потому что компилятор пытается оставить NULL-указатель инвариантом и не делает ему приведение типов, во всех остальных случаях С вызовы с первым this работают так же как и C++ (иначе как бы тогда работал COM???).
АТ>Совершенно верно. Эта проблема проявляется только на null-указателе. Я никогда не говорил, что она будет проявляться на каких-то других указателях. Примера с null совершенно достаточно для того, чтобы сделать то утверждение, которое я сделал. Где тут "неверно"?
АТ>И я все таки не понимаю, при чем тут COM. В COM в качестве указателя на интерфейс null-указатель использован быть не может.
Неверно следующее высказывание
Отсюда мораль: с ситауациях с множественным наследованием (а согласно стандарту языка — в ситуациях с любым наследованием) не надо думать о вызовах методов классов, как о вызовах обычных С функций с дополнительным параметром. Это несколько разные вещи.
Просто обратил Ваше внимание на то, что множественное наследование в данном случае не имеет никакого значения (исключение составляет только this == NULL и то будем считать, что в функции он проверяется не просто this == NULL а с помощью IsBadReadPtr), ведь COM объекты реализуются с помощью множественного наследование (в большинстве случаев) и прекрасно вызываются из C — это же самое утверждение справедливо не только для COM, но и просто для любых объектов C++. COM был назван, как самое очевидное дуальное объявление и на C++ и на C. Т.е. "мораль" неверна.
P.S. Ваш ответ абсолютно верный, но не на тот вопрос... Скорее всего, предполагалось такое использование: кто-то использует C и хочет получить библиотеку функций — человек хочет написать её на C++, использовать будут приблизительно так: LoadLibrary(...) GetProcAddress(.."newobj")... GetProcAddress(.."obj_method1") (причём obj_method1 можно сразу отмапить на метод класса)
далее
void *obj=newobj();
obj_method1(obj, "hello");
и человек спрашивает, что будет если в obj передать что-то, может быть NULL. Мне представляется, что вопрос навеян именно этим.
Здравствуйте Sergey, Вы писали:
S>Неправда ваша Если мне окончательно не отшибло память, то та версия MFC, что шла с VC 4.0, существовала не только для Win32, но и для Mac OS.
AS>>Она использует не только MS-specific код, но и ассемблерные вставки. S>Ну дык реализация библиотеки для конкретной платформы могет делать что ей заблагорассудится. Сишная RTL от MS тоже кое-где на ассемблере писана, главное чтоб интерфейс был одинаковымю
Ты не понял. Я имел в виду, что MFC не может быз изменений компилироваться на другой платформе (и даже на другом компиляторе). При этом вполне возможно её портирование на другие платформы, которое не раз и делалось. Но это уже другая история. При наличии большого желания и средств можно запортить что угодно, не то что MFC.
--
Я думал, ты огромный страшный Бажище,
А ты недоучка, крохотный Бажик...
Здравствуйте Андрей Тарасевич, Вы писали:
АТ>Из приведенной тобой выдержки, кстати, может показаться, что к статическим членам класса можно доступаться через null-указатель. Это не так. Любое вычисляемое выражение, содержащее разыменование null-указателя порождает неопределенное поведение.
А в чем это "неопределенное поведение" может выражаться при обращении через нулевой указатель к статической функции?
Здравствуйте DarkGray, Вы писали:
DG>Здравствуйте Андрей Тарасевич, Вы писали:
АТ>>Из приведенной тобой выдержки, кстати, может показаться, что к статическим членам класса можно доступаться через null-указатель. Это не так. Любое вычисляемое выражение, содержащее разыменование null-указателя порождает неопределенное поведение.
DG>А в чем это "неопределенное поведение" может выражаться при обращении через нулевой указатель к статической функции?
DG>
((S*)0)->>StaticFunc();
DG>
А кто его знает. Буквально такая запись означает
*(S*) 0;
S::StaticFunc();
Неопределенное поведение вызывается именно первым выражением. Вызов статического метода тут, собственно, ни при чем. Если некоторый компилятор возьмется транслировать этот код буквально, то в ответ на первое выражение он может сгенерировать код, загружающий разыменовываемый указатель в некий выделенный адресный регистр. Вот тут-то система и рухнет.
Например, на современной платформе x86, при попытке загрузки неправильного значения в селекторный регистр (DS, ES и т.п.) программа падает сразу, а не при попытке собственно обращения к памяти через такой регистр.
Разумеется, большинство современных компиляторов проигнорирует такое выражение. Но стандарт языка этого не требует. Любой компилятор имеет право генерировать для выражения '*(S*) 0' падающий код.
Здравствуйте Андрей Тарасевич, Вы писали:
АТ>Стандарт языка подчеркивает, что выражение, стоящее слева от '.' или '->' при доступе к члену класса (методу или полю), обязательно вычисляется. (...) Вычисление этого выражения автоматически означает разыменование использованного в нем указателя.
Да, действительно. В основном я отвечал на часть "может написано, может не написано." В смысле, что таки да, написано.
АТ>А вот например параметр 'typeid' может содержать разыменованный null-указатель, т.к. выражение-параметр 'typeid' не вычисляется.
Вычисляется в случае полиморфных типов. Может, ты имел в виду sizeof?
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте Alexander Shargin, Вы писали:
AS>Здравствуйте Sergey, Вы писали:
S>>Неправда ваша Если мне окончательно не отшибло память, то та версия MFC, что шла с VC 4.0, существовала не только для Win32, но и для Mac OS.
AS>>>Она использует не только MS-specific код, но и ассемблерные вставки. S>>Ну дык реализация библиотеки для конкретной платформы могет делать что ей заблагорассудится. Сишная RTL от MS тоже кое-где на ассемблере писана, главное чтоб интерфейс был одинаковымю
AS>Ты не понял. Я имел в виду, что MFC не может быз изменений компилироваться на другой платформе (и даже на другом компиляторе). При этом вполне возможно её портирование на другие платформы, которое не раз и делалось. Но это уже другая история. При наличии большого желания и средств можно запортить что угодно, не то что MFC.
Ну, если фразу "MFC платформеннонезависимой никогда не планировалась. Она использует не только MS-specific код, но и ассемблерные вставки." следует понимать как "MFC не может быз изменений компилироваться на другой платформе (и даже на другом компиляторе). При этом вполне возможно её портирование на другие платформы, которое не раз и делалось." то да — я этого не понял. Кстати, про другой компилятор я ничего не говорил
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Здравствуйте Андрей Тарасевич, Вы писали:
АТ>Здравствуйте Павел Кузнецов, Вы писали:
АТ>>>Может написано, может не написано. Это, в принципе, неважно. Стандарт вместо этого совершенно глухо запрещает все возможные пути падания внутрь метода объекта через null-указатель. А именно: запрещается разадресовывать null-указатель. Это автоматически означает, что, пока поведение программы определено, внутри метода объекта this никак не может быть равен null-указателю.
ПК>>AFAIK, вызов метода сам по себе не подразумевает разыменования указателя. Поэтому в стандарте (9.3.1/1) явно указано, что "If a nonstatic member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined". Т.к. null-pointer гарантированно не является указателем ни на какой объект, это предложение включает и вызов метода через null-pointer.
АТ>Я не согласен. Возможность вызова метода класса (причем как статического, так и нестатического) через null-указатель перекрыта стандартом еще раньше. Стандарт языка подчеркивает, что выражение, стоящее слева от '.' или '->' при доступе к члену класса (методу или полю), обязательно вычисляется. Причем оно вычисляется независимо от того, является ли доступаемый член класса статическим или нет. Вычисление этого выражения автоматически означает разыменование использованного в нем указателя. Разыменование null-указателя порождает неопределенное поведение.
АТ>Из приведенной тобой выдержки, кстати, может показаться, что к статическим членам класса можно доступаться через null-указатель. Это не так. Любое вычисляемое выражение, содержащее разыменование null-указателя порождает неопределенное поведение. А вот например параметр 'typeid' может содержать разыменованный null-указатель, т.к. выражение-параметр 'typeid' не вычисляется.
АТ>Также можно заметить, что если бы вызов метода не подразумевал разыменования указателя, то стандарт языка С++ должен был бы содержать отдельное замечание по поводу вызова виртуальных методов. Эти последние вызывать через null-указатель все таки нельзя (мы все в глубине души знаем, что такой вызов существенно подразумевает разыменование указателя). Я не нашел в стандарте такого замечания.
Вычисление адреса(указателя) совсем не то же самое, что и доступ(разыменование) по этому адресу. Поэтому в языке есть специальные операторы -> и * для разыменования указателей, компилятор не обращается к данным без прямого указания программиста. Что значит у Вас выражение "Вычисление этого выражения автоматически означает разыменование использованного в нем указателя"??? Разыменование — это есть способ получение данных по адресу, в Вашем выражении сколько данных будет "автоматически" получать компилятор?? 1 байт? 2 байта? или... сколько и куда???
Не надо забывать, что языки, такие как C и C++ используются для написания программ не только на PC но и для различных устройств(типа мобильных телефонов), а там организация памяти может быть абсолютно любой (т.е. по NULL адресу могут находиться системные структуры). К тому же, по Вашему заявлению, получается, что корректно заполнить таблицу прерываний с помощью C++ нельзя, ибо начинается эта таблица по адресу 0? Т.е. специально для этого надо делать ассемблерные вставки? Да, NULL (он же 0) — особое число и его можно сравнивать с указателями без приведения типа и можно сигнализировать об ошибках выделения памяти(на PC), но не более того.
Здравствуйте VVV, Вы писали:
DG>Должен ли по стандарту работать следующий код?
struct SA
{
void Func() //невиртуальная
{
if (this == NULL)
return;
}
static void StaticFunc() {}
};
void main()
{
((SA*)0)->StaticFunc(); //ИМХО, тут должно быть все нормально
((SA*)0)->Func(); //а вот здесь не понятно, мало ли чего может компилятор напихать
}
VVV>если бы Вы привели конкретное место в стандарте (хотя бы ссылку) может быть этой дискуссии и не было бы
Андрей прав. Закрываем дискуссию, вот соответствующие выдержки из стандарта:
5.2.5 Class member access (expr.ref)
1 A postfix expression followed by a dot . or an arrow ->, optionally followed by the keyword template (14.8.1), and then followed by an idexpression, is a postfix expression. The postfix expression before the dot or arrow is evaluated; 58) (...)
58) This evaluation happens even if the result is unnecessary to determine the value of the entire postfix expression, for example if the idexpression denotes a static member.
3 If E1 has the type “pointer to class X,” then the expression E1->E2 is converted to the equivalent form (*(E1)).E2; (...)
1.9 Program execution (intro.execution)
4 Certain other operations are described in this International Standard as undefined (for example, the effect of dereferencing the null pointer).
8.3.2 References (dcl.ref)
4 (...) [Note: in particular, a null reference cannot exist in a welldefined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behavior. (...)
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте VVV, Вы писали:
VVV>Вычисление адреса(указателя) совсем не то же самое, что и доступ(разыменование) по этому адресу. Поэтому в языке есть специальные операторы -> и * для разыменования указателей, компилятор не обращается к данным без прямого указания программиста. Что значит у Вас выражение "Вычисление этого выражения автоматически означает разыменование использованного в нем указателя"??? Разыменование — это есть способ получение данных по адресу, в Вашем выражении сколько данных будет "автоматически" получать компилятор?? 1 байт? 2 байта? или... сколько и куда???
Нет. Разыменованием в С++ называется применение к указателю операторов '*' или '->'. Получаются ли кем-то указуемые данные или не получаются — неважно. Если есть применение '*' и '->' в вычисляемом выражении — згачит есть разыменование.
VVV>Не надо забывать, что языки, такие как C и C++ используются для написания программ не только на PC но и для различных устройств(типа мобильных телефонов), а там организация памяти может быть абсолютно любой (т.е. по NULL адресу могут находиться системные структуры).
Конкретные компиляторы выбирают значение null-указателя таким, чтобы он не указывал ни ни какие данные, к которым может понадобится доступ из программы. Если на некотором устройстве по адресу 0x0000 сидят какие-то данные, с которыми нужно уметь работать из С/С++ программ, то в компиляторе С/С++ для этого устройства значение null-указателя будет не 0x0000, а каким-то другим, например, 0xFFFF.
Не забывай одной важного факта: то, что null-указатель получается путем преобразования целочисленной константы 0 к типу "указатель", совсем не означает, что битовое представление null-указателя стостоит из одних нулей. Т.е. это совсем не означает, что null-указатель — это указатель на адрес 0x0000. Null-указатель может представляться абсолютно любым физическим адресом. Каким — зависит от платформы.
VVV>К тому же, по Вашему заявлению, получается, что корректно заполнить таблицу прерываний с помощью C++ нельзя, ибо начинается эта таблица по адресу 0? Т.е. специально для этого надо делать ассемблерные вставки? Да, NULL (он же 0) — особое число и его можно сравнивать с указателями без приведения типа и можно сигнализировать об ошибках выделения памяти(на PC), но не более того.
Также хочу напомнить, что речь здесь идет о языках С/С++, как они описаны в своей спецификации. Разнообразные платформенно-зависимые расширения никаким боком к этой дискусии не относятся.
Если на конкретной платформе разыменование null-указателя не приводит к проблемам или даже используется для выполнения каких-то полезных дейсивий — флаг ей в руки. Но к корректному С/С++ это никаким боком относится не будет. Точнее, это вообще никаким боком к С/С++ относится не будет.
VVV>пример корректного применения NULL указателя:
С точки зрения С/С++ программа прождает неопределенное поведение, т.к. содержит разыменование null-указателя. Да и еще масса проблем (с точки зрения С/С++) в этой программе присутствует.
VVV>если бы Вы привели конкретное место в стандарте (хотя бы ссылку) может быть этой дискуссии и не было бы
Хе, хе... Что-то мне подсказывает, что приведение выдержек из стандарта дискусси не остановит. Anyway, см. письмо Павла рядом.