Я случайно вызвал функцию-член без собственного объекта класса. Но как это работает? - PullRequest
5 голосов
/ 23 февраля 2020

Вот мой код.

class IService {
};

class X_Service {
public:
    void service1() {
        std::cout<< "Service1 Running..."<<std::endl;
    }
};


int main() {
    IService service;
    auto func = reinterpret_cast<void (IService::*)()>(&X_Service::service1);
    (service.*(func))();
    return 0;
}

Я не понимаю, как это работает. Я не наследовал IService и не создавал объект X_Service, но он работает. Может кто-нибудь объяснить это?

Ответы [ 4 ]

8 голосов
/ 23 февраля 2020

Ваша путаница, вероятно, связана с неправильным пониманием того, что, поскольку что-то компилируется и работает без сбоев, оно "работает". Что не совсем верно.

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

Это означает, что может показаться, что оно работает, оно может обработать sh или может просто сделайте что-то совершенно отличное от того, что вы намеревались.

В вашем случае это работает, но все равно UB, а код недействителен.

3 голосов
/ 23 февраля 2020

Под капотом ваш компилятор превратит все эти функции в машинный код, который в основном просто переходит по определенным адресам в памяти и затем выполняет команды, хранящиеся там.

Функция-член - это просто функция, которая имеет в дополнение к локальным переменным и параметрам, часть памяти, которая хранит адрес объекта класса. Этот фрагмент памяти содержит адрес, к которому вы обращаетесь, когда используете ключевое слово * 1003.

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

Ваша функция не имеет доступа к this, поэтому ваша программа не взорвалась.

Тем не менее, это все еще неопределенное поведение, и может произойти все что угодно .

2 голосов
/ 23 февраля 2020

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

#include <iostream>

class IService {
public:
    int x;
};

class X_Service {

public:
    int x;
    void service1() {
        this->x = 65;
        std::cout << this->x << std::endl;
    }
};


int main() {
    IService service;
    auto func = reinterpret_cast<void (IService::*)()>(&X_Service::service1);
    (service.*(func))();
    std::cout << service.x << std::endl;
    std::cin.get();
    X_Service derp;
    (derp.service1)();
    std::cout << derp.x << std::endl;


    return 0;
}

Итак, с самого начала auto дал вам возможность сделать безопасный указатель без типа void (IService::*)() также экземпляр самого объекта равен this-> независимо от того, от какой функции-члена какого класса вы невидимыми унаследованы. Единственная проблема заключается в том, что первая переменная экземпляра интерпретируется на основе первой переменной класса, от которого вы скрытно наследуете, что может привести к повреждению стека, если тип отличается.

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

class IService {
public:
    char x;
};

Ваша IDE обнаружит повреждение стека вашего объекта IService, но получение вывода

65
A

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

Я также использую компилятор 86x. Так что в основном все мои переменные выстроены. Скажем, например, если я добавлю int y выше int x в Iservice, эта программа выдаст ерунду. В основном это работает только потому, что мои классы совместимы в двоичном формате.

1 голос
/ 23 февраля 2020

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

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

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

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