Версия для печати темы

Нажмите сюда для просмотра этой темы в обычном формате

Форум на CrossPlatform.RU _ С\С++ _ struct, и ее адрес.

Автор: TheGuest 9.7.2015, 21:49

Доброго времени суток. У меня вопрос.
К примеру есть структура.

struct f
{
    int x;
    int y;    
};

int main ()
{
    f a;
    f b;
    
    cout<<&a<<endl<<&b<<endl;

    cout<<&a.x<<endl<<&a.y<<endl<<endl<<endl<<&b.x<<endl<<&b.y<<endl;

    system("pause");
    return 0;
}


А теперь вопрос. Почему у структуры a и у a.x один и тот же адрес??? Ситуация с b и b.x такая же, почему так ?? Это как в одномерных массивах получается ?
Спасибо.

Автор: Гость 9.7.2015, 22:15

Цитата(Гость_TheGuest_* @ 9.7.2015, 21:49) *
Почему у структуры a и у a.x один и тот же адрес?

А почему нет? Что такое структура? Набор нескольких переменных в одном блоке. Переменные размещаются в порядке объявления. Естественно, что адрес структуры совпадёт с адресом её первого элемента.

А теперь добавьте к структуре конструктор, деструктор и какой-либо метод. Получим класс (struct в C++ - класс, где всё по умолчанию public). Для него &a и &a.x уже будут разными. (Или не будут - как компилятор разместит...)

Автор: TheGuest 9.7.2015, 22:28

Цитата(Гость @ 9.7.2015, 22:15) *
Цитата(Гость_TheGuest_* @ 9.7.2015, 21:49) *
Почему у структуры a и у a.x один и тот же адрес?

А почему нет? Что такое структура? Набор нескольких переменных в одном блоке. Переменные размещаются в порядке объявления. Естественно, что адрес структуры совпадёт с адресом её первого элемента.

А теперь добавьте к структуре конструктор, деструктор и какой-либо метод. Получим класс (struct в C++ - класс, где всё по умолчанию public). Для него &a и &a.x уже будут разными. (Или не будут - как компилятор разместит...)



Попробовал. Добавил функцию.
struct f
{
    int sum()
    {
    return 1+1;
    }
    int x;
    int y;
    
};


Но адреса остались прежними. Но все равно, спасибо Вам за разъяснения!

Автор: ahalaj 10.7.2015, 0:39

Цитата(TheGuest @ 9.7.2015, 22:28) *
Но адреса остались прежними. Но все равно, спасибо Вам за разъяснения!

Чтобы адреса были разные функция должна быть виртуальная.

Автор: TheGuest 10.7.2015, 1:07

Цитата(ahalaj @ 10.7.2015, 0:39) *
Цитата(TheGuest @ 9.7.2015, 22:28) *
Но адреса остались прежними. Но все равно, спасибо Вам за разъяснения!

Чтобы адреса были разные функция должна быть виртуальная.



Правда, как дописал virtual, адреса поменялись, а почему так происходит??

Автор: Iron Bug 10.7.2015, 9:21

Цитата(TheGuest @ 10.7.2015, 3:07) *
Правда, как дописал virtual, адреса поменялись, а почему так происходит??

потому что создаётся таблица виртуальных методов и компилятор помещает её в начале области памяти структуры (или класса).
почитай про таблицу виртуальных методов, например, https://ru.wikipedia.org/wiki/%D0%A2%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D0%B0_%D0%B2%D0%B8%D1%80%D1%82%D1%83%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D1%85_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%B2.
там очень упрощённо, на примере GCC, описана схема распределения памяти для указателей на виртуальные таблицы. компиляторы делают это немного по-разному, но таблица виртуальных методов всегда в начале блока.

P.S. это хорошо, что ты интересуешься тем, как всё устроено внутри. хорошее понимание того, как работает компилятор, никогда не помешает. а сейчас это редко встречается. обычно новички задают вопросы, как покрасить кнопочку в зелёный цвет или около того :)

Автор: Влад 10.7.2015, 11:08

Цитата(Iron Bug @ 10.7.2015, 9:21) *
потому что создаётся таблица виртуальных методов и компилятор помещает её в начале области памяти структуры (или класса). [......] компиляторы делают это немного по-разному, но таблица виртуальных методов всегда в начале блока.

Более точно будет сказать, что компилятор может разместить ТВМ в начале блока. А может - разместить где-то еще, т.к. Стандартом этот layout не специфицирован.

Автор: Iron Bug 10.7.2015, 12:30

Цитата(Влад @ 10.7.2015, 13:08) *
Более точно будет сказать, что компилятор может разместить ТВМ в начале блока. А может - разместить где-то еще, т.к. Стандартом этот layout не специфицирован.

именно это я и имела в виду. но по факту все известные мне С++ компиляторы размещают таблицу в начале блока.

Автор: Влад 10.7.2015, 13:24

Хмм. Имей в виду, что это поведение некоторых конкретных компиляторов, и полагаться, что так оно будет "всегда и везде" - крайне опасно.

Приведу пример из собственного опыта:
"Считается", что физическое представление NULL-указателя - это 0x00000000. И все известные мне компиляторы для x86 так и поступают. Даже в хидерах оно прописано.
Но вот энное время назад мне довелось поработать с архитектурой (Гарвард, ага), в которой адрес 0x00000000 был вполне себе валидным, и по этому адресу могли располагаться реальные объекты. Ага, а NULL-указатель в оной архитектуре имел физическое значение 0xFFFFFFFF...... (Си-компилер автомагически :) преобразовывал литеральный ноль в это самое значение. Что полностью соответствует требованию Библии.)
Так что, вишь как, могут быть варианты.......

Автор: TheGuest 10.7.2015, 13:48

Теперь понятно все, спасибо вам всем за разъяснения!!!

Автор: Iron Bug 10.7.2015, 22:00

Цитата(Влад @ 10.7.2015, 15:24) *
Хмм. Имей в виду, что это поведение некоторых конкретных компиляторов, и полагаться, что так оно будет "всегда и везде" - крайне опасно.
...
Так что, вишь как, могут быть варианты.......

я думаю, что мой опыт более 20 лет профессионального программирования на С/C++ под разными системами и на разных архитектурах достаточен, чтобы понимать суть вопроса. я работала с десятками разных компиляторов, и с такими, у которых NULL был определён по-другому, и с такими, у которых char был 8 байт, и с многими другими. так что я представляю себе разнообразие возможностей, как никто другой. тут вряд ли меня можно удивить.
я не предлагаю ни на что полагаться. тем более, что мне приходится писать софт под совершенно разные процессоры и я-то точно полагаться ни на что не могу даже в рамках одного проекта. только за последние полгода я работала с пятью совершенно разными архитектурами.
но компиляторов С++ не так уж много, надо сказать. и реализаций классов тоже. я думаю, что с вероятностью 99.99%, виртуальная таблица окажется в начале, до полей членов класса. просто потому, что чисто логически её там удобнее располагать. иначе пришлось бы как-то извращаться и высчитывать, где она лежит. и хранить это смещение, опять же, в начале блока с классом :) это ничего на значит. просто логическое соображение.

Автор: lanz 12.7.2015, 23:37

Не ругайтесь :lol:
Только два соображения еще:
1. В самих экземплярах классов vtable не хранится, хранится только указатель.
2. При множественном наследовании может быть несколько указателей внутри класса, при этом компилятор нигде не хранит смещение, а просто генерирует код с соответствующим смещением.
Об этом кстати написано в той же статье
https://ru.wikipedia.org/wiki/Таблица_виртуальных_методов#.D0.9C.D0.BD.D0.BE.D0.B6.D0.B5.D1.81.D1.82.D0.B2.D0.B5.D0.BD.D0.BD.D0.BE.D0.B5_.D0.BD.D0.B0.D1.81.D0.BB.D0.B5.D0.B4.D0.BE.D0.B2.D0.B0.D0.BD.D0.B8.D0.B5

Интересным следствием является то что в следующем коде

struct B1 {
    virtual ~B1() {}
};

struct B2 {
    virtual ~B2() {}
};

struct A : public B1, public B2 {
    virtual ~A() {}
};

A * aa  = new A;
B1 * b1 = aa;
B2 * b2 = aa;

b1 != b2

Автор: ViGOur 14.7.2015, 17:40

Может ты имел ввиду:
b1 == b2 == aa,
но вот
b1->vptr != b2->vptr != aa->vptr
???

Автор: lanz 14.7.2015, 18:28

Цитата
Может ты имел ввиду:
b1 == b2 == aa,
но вот
b1->vptr != b2->vptr != aa->vptr
???

Нет, именно b1 != b2 :lol:

->vptr у каждого свой, это да, но
b1 != b2
Для того чтобы компилировались одинаково
b->f1() и a->f1(), где
B1 *b = new B1;
B1 *a = new A;
Поскольку компилятор в момент вызова не знает, какой объект соответсвует a и b
А в момент присваивания знает, это его шанс :lol:

Автор: ViGOur 15.7.2015, 9:34

Хм, может я чего не понимаю, но адрес будет один и тот же, но вот различаться будет только vptr. И при присваивании aa к b1 или b2, в зависимости от типа присваивается vptr.
Проверил в Qt Creator в отладке, адрес один и тот же. :)

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

Автор: Iron Bug 15.7.2015, 9:37

да, но (*A)b1 == (*A)b2 == aa.
потому что в случае с классами для компилятора это не просто работа с указателями, а уже приведение типов и работа с виртуальными таблицами.

Автор: lanz 15.7.2015, 11:08

Цитата
Проверил в Qt Creator в отладке, адрес один и тот же. :)


Вот этот показывает разные адреса:
Раскрывающийся текст
struct B1 {
    virtual ~B1() {}
};

struct B2 {
    virtual ~B2() {}
};

struct A : public B1, public B2 {
    virtual ~A() {}
};

A * aa  = new A;
B1 * b1 = aa;
B2 * b2 = aa;

qDebug() << b1 << b2;


Цитата
И при присваивании aa к b1 или b2, в зависимости от типа присваивается vptr.

Вообще это связанно не столько с vptr сколько с наследованием.
Внутри объекта A содержится и B1 и B2.
Естественно, что они не могут оба начинатся по адресу aa+0.
Когда мы преобразуем указатель к B1( или B2) то мы получаем адрес объекта внутри A.
Он может быть как со смещением относительно aa так и без.
Но в любом случае эти адреса будут разными(при такой реализации наследования).

Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)