Указатель на адрес члена данных - PullRequest
5 голосов
/ 13 августа 2010

Я прочитал (внутри объектной модели C ++), что адрес указателя на элемент данных в C ++ является смещением элемента данных плюс 1?
Я пытаюсь это на VC ++ 2005, но я не получаю точные значения смещения.
Например:

Class X{  
  public:  
    int a;  
    int b;  
    int c;
}

void x(){  
  printf("Offsets of a=%d, b=%d, c=%d",&X::a,&X::b,&X::c);
}  

Следует печатать смещения a = 1, b = 5, c = 9. Но в VC ++ 2005 это будет a = 0, b = 4, c = 8.
Я не могу понять это поведение.
Выдержка из книги:

"Это ожидание, однако, опровергается одним - несколько традиционная ошибка для программистов на C и C ++.

Физическое смещение трех координатных членов в классе Расположение, соответственно, 0, 4 и 8, если vptr находится в конец или 4, 8 и 12, если vptr находится в начале учебный класс. Значение, возвращаемое от взятия адреса члена, однако, всегда увеличивается на 1. Таким образом, фактические значения 1, 5 и 9, и скоро. Проблема заключается в различении указателя на отсутствие данных член и указатель на первый член данных. Рассмотрим для примера:

float Point3d::*p1 = 0;   
float Point3d::*p2 = &Point3d::x;   

// oops: how to distinguish?   
if ( p1 == p2 ) {   
   cout << " p1 & p2 contain the same value — ";   
   cout << " they must address the same member!" << endl;   
}

Чтобы различать p1 и p2, каждое фактическое значение смещения элемента увеличено на 1. Следовательно, и компилятор (и пользователь) должны помнить вычесть 1 до фактического использования значения для адресации члена. "

Ответы [ 7 ]

11 голосов
/ 13 августа 2010

Смещение чего-либо - это количество единиц с начала.Во-первых, при начале, поэтому его смещение равно нулю.

Подумайте, как ваша структура находится в ячейке памяти 100:

100: class X { int a;
104:           int b;
108:           int c;

Как вы можете видетьадрес a совпадает с адресом всей структуры, поэтому его смещение (то, что вы должны добавить к адресу структуры, чтобы получить адрес элемента) равно 0.

Обратите внимание, что ISOСтандарт не указывает, где элементы расположены в памяти.Заполнение байтов для создания правильного выравнивания, безусловно, возможно.В гипотетической среде, где целые числа были только два байта, но их требуемое выравнивание было 256 байтов, они не были бы в 0, 2 и 4, а скорее в 0, 256 и 512.


И, еслита книга, из которой вы берете отрывок, действительно Inside the C++ Object Model, она становится немного длиннее в зубе.

Тот факт, что она написана в 96-м и обсуждает внутренности под C ++ (растёт лирические слова о том, насколько она хороша)это знать, где находится vptr, упуская весь смысл в том, что , работающий на неправильном уровне абстракции, и вам никогда не стоит волноваться, ) встречает его совсем немного.На самом деле, во введении даже говорится «Объясняется базовая реализация объектно-ориентированных функций ...» (мой курсив).

И тот факт, что никто не может найти что-либо в ISOстандартное высказывание о том, что такое поведение является обязательным, а также тот факт, что ни MSVC, ни gcc не действуют таким образом, заставляет меня поверить, что, даже если это было верно для одной конкретной реализации в далеком прошлом, это не так (или требуется, чтобы быть правдой)all.

Автор, по-видимому, возглавлял команды cfront 2.1 и 3, и, хотя эта книга представляет исторический интерес, я не думаю, что она имеет отношение к современному языку C ++ (и его реализации), по крайней мере, к тем битам, которые япрочитал.

8 голосов
/ 13 августа 2010

Во-первых, внутреннее представление значений указателя на тип элемента данных является деталью реализации.Это можно сделать разными способами.Вы натолкнулись на описание одной из возможных реализаций, где указатель содержит смещение члена плюс 1 .Совершенно очевидно, откуда взято это «плюс 1»: эта конкретная реализация хочет зарезервировать физическое нулевое значение (0x0) для нулевого указателя , поэтому смещение первого элемента данных (которое может легкобыть 0) должен быть преобразован во что-то другое, чтобы отличать его от нулевого указателя.Добавление 1 ко всем таким указателям решает проблему.

Однако следует отметить, что это довольно громоздкий подход (т. Е. Компилятор всегда должен вычитать 1 из физического значения перед выполнением доступа).Эта реализация, очевидно, очень старалась убедиться, что все нулевые указатели представлены физическим нулевым шаблоном.Честно говоря, я не встречал реализации, которые следуют этому подходу на практике в наши дни.

Сегодня большинство популярных реализаций (например, GCC или MSVC ++) используют только простое смещение (не добавляя к нему ничего) в качестве внутреннего представления указателя на элемент данных.Физический ноль, конечно, больше не будет работать для представления нулевых указателей, поэтому они используют некоторое другое физическое значение для представления нулевых указателей, например 0xFFFF... (это то, что используют GCC и MSVC ++).

Во-вторых,Я не понимаю, что вы пытались сказать своим примером p1 и p2.Вы абсолютно ошибаетесь, полагая, что указатели будут содержать одинаковое значение.Они не будут.

Если мы будем следовать подходу, описанному в вашем посте («смещение + 1»), тогда p1 получит физическое значение нулевого указателя (очевидно, физический 0x0), а p2получить физическое значение 0x1 (при условии, что x имеет смещение 0).0x0 и 0x1 - это два различных значения.

Если мы следуем подходу, используемому современными компиляторами GCC и MSVC ++, то p1 получит физическое значение 0xFFFF....(нулевой указатель), в то время как p2 будет присвоен физический 0x0.0xFFFF... и 0x0 - опять разные значения.

PS Я только что понял, что примеры p1 и p2 на самом деле не ваши, а цитата из книги.Что ж, в книге еще раз описывается та же проблема, о которой я упоминал выше - конфликт смещения 0 с представлением 0x0 для нулевого указателя, и предлагается один из возможных жизнеспособных подходов к решению этого конфликта.Но, опять же, есть альтернативные способы сделать это, и многие компиляторы сегодня используют совершенно разные подходы.

3 голосов
/ 13 августа 2010

Поведение, которое вы получаете, выглядит для меня вполне разумным. То, что звучит неправильно, это то, что вы читаете.

2 голосов
/ 13 августа 2010

В дополнение к ответу AndreyT: Попробуйте запустить этот код на своем компиляторе.

void test()
{  
    using namespace std;

    int X::* pm = NULL;
    cout << "NULL pointer to member: "
        << " value = " << pm 
        << ", raw byte value = 0x" << hex << *(unsigned int*)&pm << endl;

    pm = &X::a;
    cout << "pointer to member a: "
        << " value = " << pm 
        << ", raw byte value = 0x" << hex << *(unsigned int*)&pm << endl;

    pm = &X::b;
    cout << "pointer to member b: "
        << " value = " << pm 
        << ", raw byte value = 0x" << hex << *(unsigned int*)&pm << endl;
}

В Visual Studio 2008 я получаю:

NULL pointer to member:  value = 0, raw byte value = 0xffffffff
pointer to member a:  value = 1, raw byte value = 0x0
pointer to member b:  value = 1, raw byte value = 0x4

Так что, действительно, этот конкретный компилятор использует специальный битовый шаблон для представления указателя NULL и, таким образом, оставляет битовый шаблон 0x0 для представления указателя на первый член объекта.

Это также означает, что везде, где компилятор генерирует код для преобразования такого указателя в целое или логическое значение, он должен позаботиться о поиске этого специального шаблона битов. Таким образом, что-то вроде if(pm) или преобразование, выполняемое оператором потока <<, на самом деле записывается компилятором как тест против битового шаблона 0xffffffff (вместо того, как мы обычно думаем, что тесты с указателями являются необработанным тестом с адресом 0x0 ).

1 голос
/ 13 августа 2010

$ 9,2 / 12 интересно

Нестатические члены данных (не объединяющего) класса, объявленного без промежуточного спецификатора доступа, распределяются так, чтобы более поздние члены имели более высокие адреса в объекте класса. Порядок распределения нестатических элементов данных, разделенных спецификатором доступа, не определен (11.1). Требования выравнивания реализации могут привести к тому, что два смежных элемента не будут выделяться сразу после друг друга; то же самое касается требований к пространству для управления виртуальными функциями (10.3) и виртуальными базовыми классами (10.1).

Это объясняет, что такое поведение определяется реализацией. Однако тот факт, что «a», «b» и «c» находятся по возрастающим адресам, соответствует стандарту.

1 голос
/ 13 августа 2010

Я прочитал этот адрес указателя на элемент данных в C ++ является смещением член данных плюс 1?

Я никогда не слышал этого, и ваши собственные эмпирические данные показывают, что это не так. Я думаю, что вы неправильно поняли странное свойство Structs & Class в C ++. Если они полностью пусты, они, тем не менее, имеют размер 1 (поэтому каждый элемент массива имеет уникальный адрес)

0 голосов
/ 13 августа 2010

Возможно, вы захотите проверить Как объекты хранятся в памяти в C ++? , в которой более подробно говорится об этой проблеме.

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