C ++ функция вызывается без инициализации объекта - PullRequest
5 голосов
/ 19 марта 2011

Почему запускается следующий код?

#include <iostream>
class A {
    int num;
    public:
        void foo(){ num=5; std::cout<< "num="; std::cout<<num;}
};

int main() {
    A* a;
    a->foo();
    return 0;
}

Выход

num=5

Я компилирую это с помощью gcc и получаю только следующее предупреждение компилятора в строке 10:

( предупреждение: 'a' используется неинициализированным в этой функции )

Но, насколько я понимаю, этот код вообще не должен работать? И как получается, что присваивается значение 5 для num, когда num не существует, потому что еще не создан объект типа A?

Ответы [ 7 ]

4 голосов
/ 19 марта 2011

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

Вы спрашиваете, почему он работает?Это не работает.Он выдает неопределенное поведение .

Вы спрашиваете, как он присваивает 5 несуществующему члену?Это ничего не назначает.Он выдает неопределенное поведение .

Вы говорите, что вывод 5?Неправильно.Выход не 5.Там нет значимого вывода.Код производит неопределенное поведение .То, что в вашем эксперименте каким-то образом получилось печатать 5, абсолютно ничего не значит и не имеет значимого объяснения.

2 голосов
/ 19 марта 2011

Вы не инициализировали *a.

Попробуйте это:

#include <iostream>

class A
{
    int num;
    public:
        void foo(){ std::cout<< "num="; num=5; std::cout<<num;}
};

int main()
{
    A* a = new A();
    a->foo();
    return 0;
}

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

Это не безопасный код; «хакер», вероятно, мог бы использовать его.

* Конечно, даже при доступе к этому местоположению нет гарантии, что оно не будет «инициализировано» позже.


«Lucky» (на самом деле, «повезло» затрудняет отладку вашей программы):

// uninitialized memory 0x00000042 to 0x0000004B
A* a;
// a = 0x00000042;
*a = "lalalalala";
// "Nothing" happens

«Не повезло» (облегчает отладку вашей программы, поэтому я не считаю ее «везучей», на самом деле):

void* a;
// a = &main;
*a = "lalalalala";
// Not good. *Might* cause a crash.
// Perhaps someone can tell me exactly what'll happen?
2 голосов
/ 19 марта 2011

A* a; - неинициализированный указатель.

значение, которое вы видите, является мусором, и вам повезло, что вы не закончили сбоем.

здесь нет инициализации.

здесь нет назначения.

ваш класс оказывается достаточно простым, чтобы не показывать более серьезные проблемы.

A* a(0); может привести к сбою.неинициализированный указатель в некоторых случаях может привести к сбою, и его легче воспроизвести с более сложными типами.

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

1 голос
/ 20 сентября 2012

Я думаю, что так и происходит.

a->foo(); работает, потому что вы просто звоните A::foo(a).

a - переменная типа указателя, которая находится в стеке вызовов main. Функция foo() может выдать ошибку сегментации при обращении к местоположению a, но если это не так, то foo() просто перепрыгивает некоторые местоположения из a и перезаписывает 4 байта памяти значением 5. Затем она считывает то же самое значение.

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

Также посмотрите на следующий код

#include<iostream>
class A {
    int num;
    public:
        void foo(){ num=5; std::cout<< "num="; std::cout<<num;}
};

int main() {

    A* a;
    std::cout<<"sizeof A is "<<sizeof(A*)<<std::endl;
    std::cout<<"sizeof int is "<<sizeof(int)<<std::endl;
    int buffer=44;
    std::cout<<"buffer is "<<buffer<<std::endl;
    a=(A*)&buffer;

    a->foo();
    std::cout<<"\nbuffer is "<<buffer<<std::endl;
    return 0;
}
1 голос
/ 19 марта 2011
A* a;
a->foo();

Это вызывает неопределенное поведение . Чаще всего происходит сбой программы.

Раздел § 4.1 / 1 стандарта C ++ 03 гласит:

Значение (3.10) не функциональный, не массив типа T может быть преобразован в значение. Если Т является неполный тип, программа, которая требует этого преобразования плохо сформирован. Если объект, к которому Ссылка lvalue не является объектом типа T и не является объектом типа полученный из T или , если объект неинициализированная, программа, которая требует этого преобразования имеет неопределенное поведение . Если Т является неклассный тип, тип значения это CV-неквалифицированная версия T. В противном случае тип значения r Т.

См. Аналогичную тему: Где именно в стандарте C ++ говорится, что разыменование неинициализированного указателя является неопределенным поведением?


И почему он присваивает num значение 5, когда num не существует, поскольку еще не создан объект типа A.

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

0 голосов
/ 19 марта 2011

Я укажу (хе-хе) на мой предыдущий ответ на очень похожий вопрос: Крошечная программа аварийного завершения

В основном вы перезаписываете переменную стека envsваш указатель, потому что вы не добавили envs в объявление main.

Поскольку envs - это массив массивов (строк), он на самом деле очень выделен, и вы перезаписываете первый указатель в этом списке с помощью 5, а затем читаете его снова для печати с помощью cout.

Теперь это ответ на , почему это происходит.Очевидно, вы не должны полагаться на это.

0 голосов
/ 19 марта 2011

При создании объекта члены класса выделяются для этого конкретного объекта, даже если вы не используете ключевое слово new, поскольку объект является указателем на класс.Таким образом, ваш код работает нормально и дает вам значение num, но GCC выдает предупреждение, потому что вы не создали экземпляр объекта явно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...