У меня есть случай, когда друг применяет объект небазового класса типа «Базовый» в объект типа класса «Производный», где «Производный» является производным классом «Базового» и добавляет только функции, но не данные. В приведенном ниже коде я добавил элемент данных x
в производный класс.
struct A {
int a;
};
struct B : A {
// int x;
int x;
};
A a;
int g(B *b) {
a.a = 10;
b->a++;
return a.a;
}
При включенном строгом анализе псевдонимов GCC (также Clang) всегда возвращает 10
, а не 11
, потому что b
никогда не может указывать на a
в четко определенном коде. Однако, если я удалю B::x
(что на самом деле имеет место в коде моего друга), выходной ассемблерный код GCC не оптимизирует обратный доступ к a.a
и перезагружает значение из памяти. Таким образом, код моего друга, который вызывает g
, «работает» на GCC (как он и предполагал), хотя я думаю, что он все еще имеет неопределенное поведение.
g((B*)&a);
По сути, в тех же двух случаях GCC оптимизирует один случай и не оптимизирует другой случай. Это потому, что b
может тогда юридически указывать на a
? Или это потому, что GCC просто хочет не ломать реальный код?
Я проверил ответ, в котором говорится
Если вы удалите B::x, тогда B будет соответствовать требованиям 9p7 для класса со стандартной компоновкой, и доступ станет совершенно четко определенным, поскольку эти два типа совместимы с компоновкой, 9.2p17.
С двумя перечислениями, совместимыми с макетом
enum A : int { X, Y };
enum B : int { Z };
A a;
int g(B *b) {
a = Y;
*b = Z;
return a;
}
Выходные данные ассемблера для g
возвращают 1
, а не 0
, несмотря на то, что A
и B
совместимы с компоновкой (7.2p8).
Итак, мой дополнительный вопрос (цитирую ответ): "два класса с абсолютно одинаковым макетом могут считаться "почти одинаковыми" и не подлежат оптимизации".. Может ли кто-нибудь предоставить доказательства этого для GCC или Clang?
g()
называется? Сg(&a)
? Я не думаю, что использование глобальной переменной и указателя на одну и ту же глобальную переменную в одной и той же функции — это нечто иное, как поведение undefined. А, как мы знаем, неопределенное поведение может привести к самым разным вещам, включая то, что вы ожидали. - person Mats Petersson   schedule 19.06.2013g((B*)&a)
. В моем тестовом фрагменте он не вызывается (мне нужен был только вывод ассемблера дляg
) - person Johannes Schaub - litb   schedule 19.06.2013a
можно получить через выражение типаA
, но не типаB
. - person MSalters   schedule 19.06.2013-std=c++11
вместо-std=gnu++11
. - person rubenvb   schedule 19.06.2013b
, который не должен быть указателем на объектa
, на самом деле тот же самыйa.a
. Люди предпочитают быстрый код, когда он написан правильно. - person Mats Petersson   schedule 19.06.2013B
совместим сA
, они считают, чтоb
вg
может быть глобальным псевдонимомa
, а если нет, то нет. (И, конечно же, поскольку их поведение явно не определено в C++03, им не нужно делать это условно.) - person James Kanze   schedule 19.06.2013B
имеет стандартный макет, ему разрешено создавать псевдонимы, и поэтому компилятор должен фактически генерировать доступы. - person Ben Voigt   schedule 19.06.2013g
? Если нет, попробуйте сделать это. (Один из способов сделать это — сохранить адресg
в изменчивой переменной-указателе функции, а затем вызвать ее оттуда.) - person user541686   schedule 01.08.2013g
. Я только что скомпилировал его отдельно без функцииmain
. - person Johannes Schaub - litb   schedule 04.02.2017B
не объявляет какой-либо нестатический член данных или какую-либо виртуальную функцию, то компилятор предполагает, что*b
может быть псевдонимом дляa
. Может ранняя оптимизация заменяет производный класс, не добавляющий никаких данных (в т.ч. vptr) базовым? - person Oliv   schedule 07.02.2017