Re[5]: ((SA*)0)->Func();
От: VVV Россия  
Дата: 29.04.02 08:02
Оценка:
Здравствуйте Андрей Тарасевич, Вы писали:

АТ>Здравствуйте VVV, Вы писали:



VVV>>Если посмотреть с другой стороны, то любой вызов C++ функции можно описать в терминам C функции, т.е. это будет функция, где первым параметром передаётся указатель на какую-то структуру даннык (this). Посему, при вызове такой функции из C этот параметр (this) можно задать каким угодно (и NULL в том числе).


АТ>В том то и дело, что тут есть одна существенная тонкость, из-за которой вызов метода объекта в С++ нельзя так запросто описать в терминах C функций.


АТ>Предположим, что у нас имеется вот такой класс A


АТ>
АТ>struct A 
АТ>{ 
АТ>  void foo(int i) { cout << (this != NULL ? "not NULL" : "NULL") << " " << i << endl; }
АТ>};
АТ>


АТ>Перепишем метод 'A::foo' в виде "эквивалентной" С функции, как ты предлагаешь (если я тебя правильно понял):


АТ>
АТ>void foo(A* this_, int i) { cout << (this_ != NULL ? "not NULL" : "NULL") << " " << i << endl; }
АТ>


АТ>Пусть от класса 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...
Re[5]: ((SA*)0)->Func();
От: VVV Россия  
Дата: 29.04.02 08:22
Оценка:
Здравствуйте Андрей Тарасевич, Вы писали:

АТ>Здравствуйте VVV, Вы писали:



VVV>>Если посмотреть с другой стороны, то любой вызов C++ функции можно описать в терминам C функции, т.е. это будет функция, где первым параметром передаётся указатель на какую-то структуру даннык (this). Посему, при вызове такой функции из C этот параметр (this) можно задать каким угодно (и NULL в том числе).


АТ>В том то и дело, что тут есть одна существенная тонкость, из-за которой вызов метода объекта в С++ нельзя так запросто описать в терминах C функций.


АТ>Предположим, что у нас имеется вот такой класс A


АТ>
АТ>struct A 
АТ>{ 
АТ>  void foo(int i) { cout << (this != NULL ? "not NULL" : "NULL") << " " << i << endl; }
АТ>};
АТ>


АТ>Перепишем метод 'A::foo' в виде "эквивалентной" С функции, как ты предлагаешь (если я тебя правильно понял):


АТ>
АТ>void foo(A* this_, int i) { cout << (this_ != NULL ? "not NULL" : "NULL") << " " << i << endl; }
АТ>


АТ>Пусть от класса 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_ совпадают.
Re[4]: ((SA*)0)->Func();
От: Павел Кузнецов  
Дата: 29.04.02 11:47
Оценка: 3 (1)
Здравствуйте Андрей Тарасевич, Вы писали:

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.
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[5]: ((SA*)0)->Func();
От: VVV Россия  
Дата: 29.04.02 12:49
Оценка:
Здравствуйте Андрей Тарасевич, Вы писали:

АТ>Здравствуйте VVV, Вы писали:



...
АТ>В том то и дело, что тут есть одна существенная тонкость, из-за которой вызов метода объекта в С++ нельзя так запросто описать в терминах C функций.

...
АТ>Отсюда мораль: с ситауациях с множественным наследованием (а согласно стандарту языка — в ситуациях с любым наследованием) не надо думать о вызовах методов классов, как о вызовах обычных С функций с дополнительным параметром. Это несколько разные вещи.

Неверно. Все COM интерфейсы описаны и как C++ классы и как структуры на C и вызовы из C с передачей THIS первым параметром очень даже хорошо работают, хотя мы знаем, что реализуются COM объекты в большинстве случаев как классы с множественным наследованием.

    // *** IUnknown methods ***
    STDMETHOD(QueryInterface) (THIS_ REFIID riid, LPVOID FAR * lppvObj) PURE;
    STDMETHOD_(ULONG,AddRef) (THIS)  PURE;
    STDMETHOD_(ULONG,Release) (THIS) PURE;


Проблема, приведённая Вами проявляется только на указателе == NULL, потому что компилятор пытается оставить NULL-указатель инвариантом и не делает ему приведение типов, во всех остальных случаях С вызовы с первым this работают так же как и C++ (иначе как бы тогда работал COM???).
Re[6]: ((SA*)0)->Func();
От: IT Россия linq2db.com
Дата: 29.04.02 13:17
Оценка:
Здравствуйте VVV, Вы писали:

АТ>>Отсюда мораль: с ситауациях с множественным наследованием (а согласно стандарту языка — в ситуациях с любым наследованием) не надо думать о вызовах методов классов, как о вызовах обычных С функций с дополнительным параметром. Это несколько разные вещи.


VVV>Неверно. Все COM интерфейсы описаны и как C++ классы и как структуры на C и вызовы из C с передачей THIS первым параметром очень даже хорошо работают, хотя мы знаем, что реализуются COM объекты в большинстве случаев как классы с множественным наследованием.


Ты не совсем внимательно читал поедыдущий постинг. Множественное наследование интерфейсов (я позволю себе ввести такой термин) и наследование регулярных классов C++ немного отличаются. Интерфейсы можно рассматривать как упрощённые классы, но упрощенные настолько, что это упрощение снимает большинство проблем связанных с множественным наследованием. У интерфейсов нет данных, а это самая большая проблема при множественном наследовании и у Андрея в "экземпляр класса A внутри экземпляра класса C" как раз и идёт речь не об указателях на таблицы виртуальных методов, а именно об экземплярах данных этих классов.

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

VVV>Проблема, приведённая Вами проявляется только на указателе == NULL, потому что компилятор пытается оставить NULL-указатель инвариантом и не делает ему приведение типов, во всех остальных случаях С вызовы с первым this работают так же как и C++ (иначе как бы тогда работал COM???).


Компилятору по барабану какое там значение у указателя, NULL или не NULL.
Если нам не помогут, то мы тоже никого не пощадим.
Re[7]: ((SA*)0)->Func();
От: VVV Россия  
Дата: 29.04.02 13:47
Оценка:
Здравствуйте 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;
}

теперь ассемблер
27:   int main(int argc, char* argv[])
28:   {
00411680   push        ebp
00411681   mov         ebp,esp
00411683   sub         esp,48h
00411686   push        ebx
00411687   push        esi
00411688   push        edi
00411689   lea         edi,[ebp-48h]
0041168C   mov         ecx,12h
00411691   mov         eax,0CCCCCCCCh
00411696   rep stos    dword ptr [edi]
29:       C *p=(C*)NULL;
00411698   mov         dword ptr [ebp-4],0
30:       p->foo(5);
0041169F   push        5
004116A1   mov         ecx,dword ptr [ebp-4]
004116A4   add         ecx,4
004116A7   call        @ILT+15(A::foo) (00401014)
31:
32:       foo(p, 5);
004116AC   cmp         dword ptr [ebp-4],0
004116B0   je          main+3Dh (004116bd)
004116B2   mov         eax,dword ptr [ebp-4]
004116B5   add         eax,4
004116B8   mov         dword ptr [ebp-8],eax
004116BB   jmp         main+44h (004116c4)
004116BD   mov         dword ptr [ebp-8],0
004116C4   push        5
004116C6   mov         ecx,dword ptr [ebp-8]
004116C9   push        ecx
004116CA   call        @ILT+55(foo) (0040103c)
004116CF   add         esp,8
33:
34:       return 0;
004116D2   xor         eax,eax
35:   }


т.е. во втором случае идёт сравнение с 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;
}

и т.д.
)
Re[6]: ((SA*)0)->Func();
От: Андрей Тарасевич Беларусь  
Дата: 29.04.02 14:15
Оценка:
Здравствуйте VVV, Вы писали:

VVV>...

АТ>>В том то и дело, что тут есть одна существенная тонкость, из-за которой вызов метода объекта в С++ нельзя так запросто описать в терминах C функций.

VVV>...

АТ>>Отсюда мораль: с ситауациях с множественным наследованием (а согласно стандарту языка — в ситуациях с любым наследованием) не надо думать о вызовах методов классов, как о вызовах обычных С функций с дополнительным параметром. Это несколько разные вещи.

VVV>Неверно. Все COM интерфейсы описаны и как C++ классы и как структуры на C и вызовы из C с передачей THIS первым параметром очень даже хорошо работают, хотя мы знаем, что реализуются COM объекты в большинстве случаев как классы с множественным наследованием.


Что значит "неверно"? В-первых, реализация COM-интерфейсов зафиксирована на очень низком уровне (бинараная совместимось), и "подтасована" под организацию таблиц виртуальных методов конкретным компилятором. К языку С++ это никакого отношения не имеет. Мое же утверждение относилось именно к языку С++.

VVV>Проблема, приведённая Вами проявляется только на указателе == NULL, потому что компилятор пытается оставить NULL-указатель инвариантом и не делает ему приведение типов, во всех остальных случаях С вызовы с первым this работают так же как и C++ (иначе как бы тогда работал COM???).


Совершенно верно. Эта проблема проявляется только на null-указателе. Я никогда не говорил, что она будет проявляться на каких-то других указателях. Примера с null совершенно достаточно для того, чтобы сделать то утверждение, которое я сделал. Где тут "неверно"?

И я все таки не понимаю, при чем тут COM. В COM в качестве указателя на интерфейс null-указатель использован быть не может.
Best regards,
Андрей Тарасевич
Re[6]: ((SA*)0)->Func();
От: Андрей Тарасевич Беларусь  
Дата: 29.04.02 14:23
Оценка:
Здравствуйте 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_ совпадают.


Разумеется. Об этом и идет речь в моем предыдущем сообщении. Так что именно ты хочешь этим сказать?
Best regards,
Андрей Тарасевич
Re[8]: ((SA*)0)->Func();
От: IT Россия linq2db.com
Дата: 29.04.02 15:03
Оценка:
Здравствуйте VVV, Вы писали:

VVV>В том то и дело, что не "по барабану"...

VVV>т.е. во втором случае идёт сравнение с 0 и если не 0, то только тогда компилятор приводит тип.

Зря он это делает, может он ещё и проверку выхода за границы массивов мне делать будет

VVV>насчёт наследования:

[skip]
VVV> разве это не нормальный COM объект?

Это нормальный C++ класс, реализующий интерфейс IUnknown. COM объектом он станет, когда ты его создашь посредством одной из функций в контексте COM.

VVV>(конечно, наружу я выставлю примерно такое:


Вот-вот, и это правильно, наружу выставляется именно интерфейс.
Хочу ещё раз повториться. Интерфейс — это не полноценный C++ класс. Упрощенно его реализацию можно рассматривать как указатель на последовательную группу виртуальных функций внутри общей таблицы виртуальных методов. При этом никакие данные (поля) класса его не касаются. В языках, где запрещено множественное наследование классов, как правило разрешено множественное наследование интерфейсов, т.к. это легко реализуемо и не вносит в описание класса неразберихи.
Если нам не помогут, то мы тоже никого не пощадим.
Re[9]: ((SA*)0)->Func();
От: Павел Кузнецов  
Дата: 29.04.02 15:27
Оценка: 7 (1)
Здравствуйте 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
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[10]: ((SA*)0)->Func();
От: IT Россия linq2db.com
Дата: 29.04.02 15:35
Оценка:
Здравствуйте Павел Кузнецов, Вы писали:

ПК>Не зря. Если компилятор не сгенерирует код для проверки на null-pointer при преобразовании указателя, то в последующем, при сравнении полученного после преобразования 0 (NULL) указателя с 0 (NULL), результат может быть самым неожиданным.


Да, ты прав. Я закциклился на первоначальной теме топика
Если нам не помогут, то мы тоже никого не пощадим.
Re[7]: ((SA*)0)->Func();
От: VVV Россия  
Дата: 29.04.02 17:25
Оценка:
Здравствуйте Андрей Тарасевич, Вы писали:

АТ>Здравствуйте VVV, Вы писали:


VVV>>оказалось, что как раз VC 6.0 в случае foo(p, 5); проверяет p на 0 и не делает смещения в 4 байта, только если p равен NULL, в приведённом же примере где C *p=(C*)1 this и this_ совпадают.


АТ>Разумеется. Об этом и идет речь в моем предыдущем сообщении. Так что именно ты хочешь этим сказать?


То же, что и в самом начале — что вызовы C++ функций могут быть проэмулированы вызовами C функций с передачей this первым параметром.
Re[8]: ((SA*)0)->Func();
От: Андрей Тарасевич Беларусь  
Дата: 29.04.02 17:33
Оценка:
Здравствуйте VVV, Вы писали:

АТ>>Разумеется. Об этом и идет речь в моем предыдущем сообщении. Так что именно ты хочешь этим сказать?


VVV>То же, что и в самом начале — что вызовы C++ функций могут быть проэмулированы вызовами C функций с передачей this первым параметром.


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

Но в этой дискуссии речь идет о [нелегальном] вызове метода класса через null-указатель не объект (обрати внимание на поле "тема"). Как только речь идет о вызове метода класса через null-указатель на объект, обычный вызов метода и вышеупомянутая эмуляция дают (могут давать) разные результаты. Я хотел сказать только это и ничего больше.
Best regards,
Андрей Тарасевич
Re[7]: ((SA*)0)->Func();
От: VVV Россия  
Дата: 29.04.02 17:49
Оценка:
Здравствуйте Андрей Тарасевич, Вы писали:

АТ>Здравствуйте 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. Мне представляется, что вопрос навеян именно этим.


Спасибо за дискуссию :)
Re[10]: ((SA*)0)->Func();
От: Alexander Shargin Россия RSDN.ru
Дата: 29.04.02 17:52
Оценка:
Здравствуйте Sergey, Вы писали:

S>Неправда ваша Если мне окончательно не отшибло память, то та версия MFC, что шла с VC 4.0, существовала не только для Win32, но и для Mac OS.


AS>>Она использует не только MS-specific код, но и ассемблерные вставки.

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

Ты не понял. Я имел в виду, что MFC не может быз изменений компилироваться на другой платформе (и даже на другом компиляторе). При этом вполне возможно её портирование на другие платформы, которое не раз и делалось. Но это уже другая история. При наличии большого желания и средств можно запортить что угодно, не то что MFC.
--
Я думал, ты огромный страшный Бажище,
А ты недоучка, крохотный Бажик...
((SA*)0)->Func();
От: Андрей Тарасевич Беларусь  
Дата: 29.04.02 18:05
Оценка: 9 (2)
#Имя: FAQ.cpp.*nullptr
DG>Должен ли по стандарту работать следующий код?

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-указатель все таки нельзя (мы все в глубине души знаем, что такой вызов существенно подразумевает разыменование указателя). Я не нашел в стандарте такого замечания.
Best regards,
Андрей Тарасевич
Re[6]: ((SA*)0)->Func();
От: DarkGray Россия http://blog.metatech.ru/post/ogni-razrabotki.aspx
Дата: 29.04.02 20:07
Оценка:
Здравствуйте Андрей Тарасевич, Вы писали:

АТ>Из приведенной тобой выдержки, кстати, может показаться, что к статическим членам класса можно доступаться через null-указатель. Это не так. Любое вычисляемое выражение, содержащее разыменование null-указателя порождает неопределенное поведение.


А в чем это "неопределенное поведение" может выражаться при обращении через нулевой указатель к статической функции?

((S*)0)->StaticFunc();
Re[7]: ((SA*)0)->Func();
От: Андрей Тарасевич Беларусь  
Дата: 29.04.02 20:41
Оценка:
Здравствуйте DarkGray, Вы писали:

DG>Здравствуйте Андрей Тарасевич, Вы писали:


АТ>>Из приведенной тобой выдержки, кстати, может показаться, что к статическим членам класса можно доступаться через null-указатель. Это не так. Любое вычисляемое выражение, содержащее разыменование null-указателя порождает неопределенное поведение.


DG>А в чем это "неопределенное поведение" может выражаться при обращении через нулевой указатель к статической функции?


DG>
((S*)0)->>StaticFunc();
DG>


А кто его знает. Буквально такая запись означает

*(S*) 0;
S::StaticFunc();


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

Например, на современной платформе x86, при попытке загрузки неправильного значения в селекторный регистр (DS, ES и т.п.) программа падает сразу, а не при попытке собственно обращения к памяти через такой регистр.

Разумеется, большинство современных компиляторов проигнорирует такое выражение. Но стандарт языка этого не требует. Любой компилятор имеет право генерировать для выражения '*(S*) 0' падающий код.
Best regards,
Андрей Тарасевич
Re[6]: ((SA*)0)->Func();
От: Павел Кузнецов  
Дата: 30.04.02 05:15
Оценка:
Здравствуйте Андрей Тарасевич, Вы писали:

АТ>Стандарт языка подчеркивает, что выражение, стоящее слева от '.' или '->' при доступе к члену класса (методу или полю), обязательно вычисляется. (...) Вычисление этого выражения автоматически означает разыменование использованного в нем указателя.


Да, действительно. В основном я отвечал на часть "может написано, может не написано." В смысле, что таки да, написано.

АТ>А вот например параметр 'typeid' может содержать разыменованный null-указатель, т.к. выражение-параметр 'typeid' не вычисляется.


Вычисляется в случае полиморфных типов. Может, ты имел в виду sizeof?
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[11]: ((SA*)0)->Func();
От: Sergey Россия  
Дата: 30.04.02 05:54
Оценка:
Здравствуйте 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 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.