Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: struct, и ее адрес.
Форум на CrossPlatform.RU > Разработка > С\С++
TheGuest
Доброго времени суток. У меня вопрос.
К примеру есть структура.

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 такая же, почему так ?? Это как в одномерных массивах получается ?
Спасибо.
Гость
Цитата(Гость_TheGuest_* @ 9.7.2015, 21:49) *
Почему у структуры a и у a.x один и тот же адрес?

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

А теперь добавьте к структуре конструктор, деструктор и какой-либо метод. Получим класс (struct в C++ - класс, где всё по умолчанию public). Для него &a и &a.x уже будут разными. (Или не будут - как компилятор разместит...)
TheGuest
Цитата(Гость @ 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
Цитата(TheGuest @ 9.7.2015, 22:28) *
Но адреса остались прежними. Но все равно, спасибо Вам за разъяснения!

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

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



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

потому что создаётся таблица виртуальных методов и компилятор помещает её в начале области памяти структуры (или класса).
почитай про таблицу виртуальных методов, например, здесь.
там очень упрощённо, на примере GCC, описана схема распределения памяти для указателей на виртуальные таблицы. компиляторы делают это немного по-разному, но таблица виртуальных методов всегда в начале блока.

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

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

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

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

я думаю, что мой опыт более 20 лет профессионального программирования на С/C++ под разными системами и на разных архитектурах достаточен, чтобы понимать суть вопроса. я работала с десятками разных компиляторов, и с такими, у которых NULL был определён по-другому, и с такими, у которых char был 8 байт, и с многими другими. так что я представляю себе разнообразие возможностей, как никто другой. тут вряд ли меня можно удивить.
я не предлагаю ни на что полагаться. тем более, что мне приходится писать софт под совершенно разные процессоры и я-то точно полагаться ни на что не могу даже в рамках одного проекта. только за последние полгода я работала с пятью совершенно разными архитектурами.
но компиляторов С++ не так уж много, надо сказать. и реализаций классов тоже. я думаю, что с вероятностью 99.99%, виртуальная таблица окажется в начале, до полей членов класса. просто потому, что чисто логически её там удобнее располагать. иначе пришлось бы как-то извращаться и высчитывать, где она лежит. и хранить это смещение, опять же, в начале блока с классом :) это ничего на значит. просто логическое соображение.
lanz
Не ругайтесь :lol:
Только два соображения еще:
1. В самих экземплярах классов vtable не хранится, хранится только указатель.
2. При множественном наследовании может быть несколько указателей внутри класса, при этом компилятор нигде не хранит смещение, а просто генерирует код с соответствующим смещением.
Об этом кстати написано в той же статье
https://ru.wikipedia.org/wiki/Таблица_вирту....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
Может ты имел ввиду:
b1 == b2 == aa,
но вот
b1->vptr != b2->vptr != aa->vptr
???
lanz
Цитата
Может ты имел ввиду:
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
Хм, может я чего не понимаю, но адрес будет один и тот же, но вот различаться будет только vptr. И при присваивании aa к b1 или b2, в зависимости от типа присваивается vptr.
Проверил в Qt Creator в отладке, адрес один и тот же. :)

Если я не прав, ткните меня в чем, чтобы закрыть пробел.
Iron Bug
да, но (*A)b1 == (*A)b2 == aa.
потому что в случае с классами для компилятора это не просто работа с указателями, а уже приведение типов и работа с виртуальными таблицами.
lanz
Цитата
Проверил в 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 так и без.
Но в любом случае эти адреса будут разными(при такой реализации наследования).
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Форум IP.Board © 2001-2024 IPS, Inc.