Здравствуйте. Я новичок.Сейчас пытаюсь разобраться с темой указателей. В книге которую читаю есть пример "выбор данных из памяти с помощью разных указателей"
unsigned long L = 0x12345678L;
char *cp = (char*)&L;
int* ip = (int*)&L;
cout<<hex;
сout<<"&L = "<<&L<<endl;
cout<<"cp = "<<(void*)cp<<"\t *cp = 0x"<<*cp<<endl;
сout<<"ip = "<<(void*)ip<<"\t *ip = 0x"<<*ip<<endl;
соответственно результат выполнения:
&L = 0x1E190FFC
cp = 0x1E190FFC *cp = 0x78
ip = 0x1E190FFC *ip = 0x5678
Когда речь идет о преобразовании обычных типов вроде все ясно(для чего и что происходит).
А вот когда вижу
char *cp = (char*)&L;
int* ip = (int*)&L;
Мне не совсем понятно что фактически происходит в преобразовании (char*)&L или (int*)&L. В приведенном выше примере результат программы показывает что адрес L и значение указателей cp и ip фактически одинаково = 0x1E190FFC. Это ведь адрес ячейки памяти, зачем его приводить к типу? При этом как я понимаю что значением указателя ip например, является адрес переменной L и при обращении к ее значению через операцию разыменования (*ip) из 4-х байт переменной L будет выбрано 2 байта информации поскольку указатель имеет тип int. Это демонстрирует и результат программы. Что происходит фактически с адресом &L когда его приводят к типу (char*) или (int*) и происходит ли вообще??? Он ведь остается прежним! Поясните пожалуйста момент приведения к типу указателя. Заранее спасибо.
Здравствуйте, b_mike, Вы писали:
_>Здравствуйте. Я новичок.Сейчас пытаюсь разобраться с темой указателей. В книге которую читаю есть пример "выбор данных из памяти с помощью разных указателей"
_>_>unsigned long L = 0x12345678L;
_>char *cp = (char*)&L;
_>int* ip = (int*)&L;
_>cout<<hex;
_>сout<<"&L = "<<&L<<endl;
_>cout<<"cp = "<<(void*)cp<<"\t *cp = 0x"<<*cp<<endl;
_>сout<<"ip = "<<(void*)ip<<"\t *ip = 0x"<<*ip<<endl;
_>соответственно результат выполнения:
_>&L = 0x1E190FFC
_>cp = 0x1E190FFC *cp = 0x78
_>ip = 0x1E190FFC *ip = 0x5678
_>
_>Когда речь идет о преобразовании обычных типов вроде все ясно(для чего и что происходит).
_>А вот когда вижу
_>_>char *cp = (char*)&L;
_>int* ip = (int*)&L;
_>
_>Мне не совсем понятно что фактически происходит в преобразовании (char*)&L или (int*)&L. В приведенном выше примере результат программы показывает что адрес L и значение указателей cp и ip фактически одинаково = 0x1E190FFC. Это ведь адрес ячейки памяти, зачем его приводить к типу? При этом как я понимаю что значением указателя ip например, является адрес переменной L и при обращении к ее значению через операцию разыменования (*ip) из 4-х байт переменной L будет выбрано 2 байта информации поскольку указатель имеет тип int. Это демонстрирует и результат программы. Что происходит фактически с адресом &L когда его приводят к типу (char*) или (int*) и происходит ли вообще??? Он ведь остается прежним! Поясните пожалуйста момент приведения к типу указателя. Заранее спасибо.
Адрес остается прежним, но зато меняется интерпретация этих данных по этому адресу, а также меняется арифметика указателей, так как каждый раз вы указываете определенный тип указателя.
Например, если у вас есть предложение из вашего примера
char *cp = (char*)&L;
то ++cp сместиться только на размер байта. То есть вы побайтно сможете анализировать значения, расположенные в исходном объекте.
Другой пример.
int x = -10;
unsigned int *p = ( unsigned int * ) &x;
В этом случае вы уже можете манипулировать значением x через указатель p, как будто бы оно беззнаковое целое число.
Здравствуйте, b_mike, Вы писали:
_>Здравствуйте. Я новичок.Сейчас пытаюсь разобраться с темой указателей. В книге которую читаю есть пример "выбор данных из памяти с помощью разных указателей"
_>...
_>Мне не совсем понятно что фактически происходит в преобразовании (char*)&L или (int*)&L. В приведенном выше примере результат программы показывает что адрес L и значение указателей cp и ip фактически одинаково = 0x1E190FFC. Это ведь адрес ячейки памяти, зачем его приводить к типу?
Это называется типобезопасность.
Поразмышляйте над следующим псевдокодом:
unsigned int SomeValue = 0xFFFFFFFF;
int AnotherValue = SomeValue;
Вопрос — чему будет равно AnotherValue ? Будет равно -1. Потому что SomeValue — это беззнаковое
целое, а AnotherValue — целое со знаком, во время присваивания произошло преобразование типов, а
значение 0xFFFFFFFF в знаковой форме соответствует минус единице. Подобные "фокусы" несут в себе
много потенциальных опасностей, поэтому компилятор не даст скомпилироваться данному примеру и
покажет сообщение об ошибке, что-то вроде "cannot convert from <int> to <unsigned int>".
Но если мы точно знаем, что делаем, мы можем разрешить подобное приведение типов явным образом:
unsigned int SomeValue = 0xFFFFFFFF;
int AnotherValue = (int)SomeValue;
Здесь мы явно указываем, что соглашаемся с последствиями, к которым может привести
преобразование unsigned int в int. Такая форма называется приведением в стиле C и
считается опасной, в первую очередь потому, что позволяет без разбору выполнять
приведения несовместимых между собой типов. Например, указатель в char, int в void * и т.д.
Когда-нибудь исходный тип может измениться, но компилятор спокойно проглотит его
приведение в несовместимую форму где-нибудь в совершенно другой части кода, и из-за
этого полезут противные и труднообнаруживаемые баги.
В C++ рекомендуется использовать новые механизмы приведения типов — это static_cast, в
первую очередь, а также dynamic_cast, const_cast и reinterpret_cast.
static_cast не позволяет "просто так" привести указатель в int, например, и
считается намного более безопасным, чем обычное приведение в стиле C.
С учетом сказанного, код лучше переписать так:
unsigned int SomeValue = 0xFFFFFFFF;
int AnotherValue = static_cast<int>(SomeValue);
В Вашем примере речь идет об указателях.
Здесь нужно иметь в виду дополнительное условие — арифметика указателей.
Указатель на char предназначен для доступа к char-ам, указатель на int — для
доступа к int-ам и т.д. Если мы через указатель на char прочитаем int-овское
значение, то получим только ту его часть, которая укладывается в размер char.
Если мы через указатель на int попробуем записать что-нибудь в char-переменную, то,
скорее всего, словим ошибку доступа к памяти и программа будет завершена аварийно.
Либо попортим соседнюю переменную или участок кода и вообще вылетим неизвестно куда.
А инкремент указателя приводит к увеличению его скалярного значения на размер типа.
Например, ++<char> делает инкремент на sizeof (char) байт, ++<int> на sizeof (int) байт,
++<my_class> на sizeof (my_class) байт и т.д. Если указатель на int установить на
массив char-ов, а затем попытаться "пройтись" по этому массиву, инкрементируя
указатель на каждой итерации, мы получим неверные значения, а то и вовсе ошибку доступа,
потому что указатель каждый раз будет увеличиваться не на <char> байт, как должен был бы, а
на <int> байт.
Ввиду обозначенных причин компилятор также не посмеет "бездумно" приводить один
тип указателя к другому, ибо их арифметика отличается.
В заключение следует отметить, что приведения типов вроде тех, которые были продемонстрированы,
не несут никаких накладных расходов и сгенерированные машинные команды получаются такими,
как если бы значения присваивались напрямую, без приведений типов. Сами приведения нужны
только для обеспечения удобств программирования, точно так же, как public/private или typedef.
On 03/31/2012 09:19 PM, b_mike wrote:
> Когда речь идет о преобразовании обычных типов вроде все ясно(для чего и что
> происходит).
> А вот когда вижу
>
> char *cp = (char*)&L;
> int* ip = (int*)&L;
А чем тебе char & int необычны ? Это -- обычные типы языка С/С++.
Это ведь адрес
> ячейки памяти, зачем его приводить к типу?
Чтобы компилятор интерпретировал этот адрес (один и тот же) в дальнейшем как
указатель на переменную определённого типа.
При этом как я понимаю что значением
> указателя ip например, является адрес переменной L и при обращении к ее значению
> через операцию разыменования (*ip) из 4-х байт переменной L будет выбрано 2
> байта информации поскольку указатель имеет тип int.
int и long в современных платформах оба 4 байта.
Это демонстрирует и
> результат программы. Что происходит фактически с адресом &L когда его приводят к
> типу (char*) или (int*) и происходит ли вообще???
Он в дальнейшем интерпретируется как адрес переменной того типа, к указателю на
который приводится. Память не меняется, адрес не меняется, меняется то,
как компилятор будет обрабатывать данные по этому адресу.
(char*) -- будет считать, что там лежит код символа (8-битное число)
(int*) -- будет считать, что там лежит целое число, как правило, 4 байтовое,
знаковое.
Posted via RSDN NNTP Server 2.1 beta