Здравствуйте Андрей Тарасевич, Вы писали:
АТ>Здравствуйте Павел Кузнецов, Вы писали:
АТ>>>Может написано, может не написано. Это, в принципе, неважно. Стандарт вместо этого совершенно глухо запрещает все возможные пути падания внутрь метода объекта через 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), но не более того.
пример корректного применения NULL указателя:
#include "stdafx.h"
class Mem{
public:
unsigned char m[0x400000];
unsigned char prg[256];
void Print()
{
for(int i=0; i < 256; i++)
printf("%c", prg[i] >= (unsigned char)' ' ? prg[i] : '.');
printf("\n");
}
};
int main(int argc, char* argv[])
{
Mem *pm=(Mem*)NULL;
for(int i=0; i < 256; i++)
printf("%c", pm->prg[i] >= (unsigned char)' ' ? pm->prg[i] : '.');
printf("\n");
pm->Print();
return 0;
}
если бы Вы привели конкретное место в стандарте (хотя бы ссылку) может быть этой дискуссии и не было бы :)
Здравствуйте 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, см. письмо Павла рядом.