Присвоение значений указателю

У меня небольшие проблемы с пониманием концепции указателей, и один из них таков:
Давайте объявим переменную целочисленного типа n и указатель на нее *p.
int n=23,*p;
Теперь,
> p=&n;, если я не ошибаюсь, присваивает адрес переменной n (скажем, 3000) p.
Таким образом, cout<<p<<" "<<*p; выводит 3000 и 23 соответственно.
Я сомневаюсь, что предположим, что мы сделали что-то вроде этого:
p=5; т.е. присвоение числового значения переменной, предназначенной для хранения ячеек памяти, что произойдет?
Перемещена ли переменная в ячейку памяти '5' (скорее всего, нет) или указатель только что преобразован в «int» и сделан для хранения значения 5? Я бы попробовал это сам, только возня с памятью моей системы заставило меня задуматься.

Кроме того, когда мы объявляем любую переменную (предположим, int с пространством в 2 байта), сохраняется ли она в случайном месте памяти, таком как 3000, 101, 2700 или что-то в этом роде, или сохраняется в 0,2,4 и т. д.? И хранится ли следующая объявленная переменная прямо в следующей (например, 3002, 103 или 2702) или между ними есть какой-то промежуток?


person Anchith Acharya    schedule 27.01.2017    source источник
comment
Вы не можете этого сделать, 5 не конвертируется в int*. (Если бы вы действительно попробовали это сделать, то, вероятно, обнаружили бы это сами.)   -  person Kerrek SB    schedule 27.01.2017
comment
Это нормально, это безопасно, вы можете проверить это на своей машине. Учитесь на ошибках, это лучший способ. Вы получите хорошую ошибку компилятора.   -  person YSC    schedule 27.01.2017
comment
@KerrekSB На самом деле, он может сделать это вручную, написав p = (int*)5;   -  person alexeykuzmin0    schedule 27.01.2017
comment
Если вы знаете значение действительного адреса правильного типа, все в порядке. Если вы используете адрес другого типа или просто недопустимый адрес, это UB. Я смотрел доклад, в котором они использовали современный C++ для программирования pong на commodore 64, и им пришлось это сделать, поскольку он использует устройства с отображением памяти.   -  person NathanOliver    schedule 27.01.2017
comment
@NathanOliver Также стоит отметить, что на 64-битной машине правильный адрес не обязательно помещается в int, поэтому для таких экспериментов лучше использовать std::ptrdiff_t.   -  person alexeykuzmin0    schedule 27.01.2017
comment
Иногда использование музыки в качестве отсылки к повседневному юмору может быть полезным, чтобы поднять настроение и отвлечься от повседневных проблем, поэтому в отношении этого вопроса: верите ли вы в магию? Что ж, в C++ нет никакой магии; извините, что разорвал ваш пузырь...   -  person Francis Cugler    schedule 27.01.2017
comment
@alexeykuzmin0 На самом деле я думаю, что intptr_t - это подходящий стандартный тип.   -  person NathanOliver    schedule 27.01.2017
comment
@NathanOliver Ты прав   -  person alexeykuzmin0    schedule 27.01.2017
comment
Вот мой ответ на аналогичный вопрос несколько лет назад: отвеченный вопрос   -  person Francis Cugler    schedule 27.01.2017


Ответы (5)


В вашем примере это ошибка компилятора.

Однако я предполагаю, что вы хотели сделать следующее:

int n =23, *p;
p = &n;
//Change the value of p to 3000, p now points to address 3000
p = reinterpret_cast<int*>(3000); 
//Check if the address of n has changed
std::cout << "Address of n : " << reinterpret_cast<int>(&n) << std::endl; 

Как вы можете сказать, когда вы запускаете этот код. Адрес не меняется.

По вашему второму вопросу.

И да и нет :)

Если вы определяете две переменные рядом друг с другом, они могут быть рядом друг с другом в памяти.

 int a,b,c,d;
 char c = 1;
 short s = 1;
 void* p = nullptr;
 int i = 1;

 std::cout << "a is at: " << reinterpret_cast<int>(&a) << std::endl;
 std::cout << "b is at: " << reinterpret_cast<int>(&b) << std::endl;
 std::cout << "c is at: " << reinterpret_cast<int>(&c) << std::endl;
 std::cout << "d is at: " << reinterpret_cast<int>(&d) << std::endl;
 std::cout << "Char is at: " << reinterpret_cast<int>(&c) << std::endl;
 std::cout << "Short is at: " << reinterpret_cast<int>(&s) << std::endl;
 std::cout << "Pointer is at: " << reinterpret_cast<int>(p) << std::endl;
 std::cout << "Int is at: " << reinterpret_cast<int>(&i) << std::endl;

Такое поведение вызвано компилятором, определяющим, куда все втыкать. Они могут или не могут существовать рядом друг с другом. Если вы хотите гарантировать, что они существуют рядом друг с другом, используйте массив.

int arr[] = {1,2,3,4,5,6,7};
int * p = &arr[0]; //get address of first element
for(int i = 0 ; i < 7; ++i)
    std::cout << "Value at address: " << reinterpret_cast<int>(p+i) 
        << " is: " << *( p + i) << std::endl;
person user3853544    schedule 27.01.2017
comment
Где хранятся переменные (относительно друг друга) определяется вашим компилятором, а не ОС. - person melpomene; 27.01.2017
comment
@melpomene, моя ошибка исправлена. - person user3853544; 27.01.2017
comment
Хотя ОС сама определит, какое место в памяти она предоставит программе для автоматического сохранения. - person NathanOliver; 27.01.2017
comment
Глупый я путаю виртуальный и физический - person user3853544; 27.01.2017
comment
Это также зависит от единицы выделения памяти; или тип классификатора памяти, например: global, stack or local, from the heap или static. Есть и другие, но это 4 наиболее распространенные единицы трансляции памяти, и они зависят от компилятора, ОС и архитектуры вашей системы. Причина, по которой я включил все 3, заключается в том, что каждый компилятор обрабатывает свои кадры стека и кучу по-разному, каждая ОС управляет или обращается к ним по-разному, и архитектура может даже хранить их по-разному, например, в Little и Big Endian. Также следует учитывать энергозависимую и изменяемую память. - person Francis Cugler; 27.01.2017
comment
(...продолжение) но большая часть семантики построения и работы стека и кучи скрыта компилятором и оптимизацией компилятора; вам все равно нужно знать и опасаться их там, где это вызывает большую озабоченность, когда вы работаете непосредственно в ASM (сборке). - person Francis Cugler; 27.01.2017

Предполагая, что вы скомпилируете его, добавив приведение

p=(int *)5;

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

Кроме того, какой адрес получает переменная или следующая, также зависит от системы, в которой вы запускаете свою программу. Большинство систем используют стек для локальных переменных, но они могут считать вверх или вниз, так что вы не знаете.

Сборки отладки и оптимизации также могут создавать разный код. Некоторые компиляторы оставляют место после локальных массивов или переменных, поэтому они могут выдавать осмысленную ошибку, когда вы пишете вне массива. Но в релизе они могут использовать память как можно плотнее.

Чтобы сделать ситуацию еще более непредсказуемой, основные операционные системы имеют метод, называемый рандомизацией адресного пространства (ASLR).

https://en.wikipedia.org/wiki/Address_space_layout_randomization

Базовый адрес, который получают переменные, будет рандомизирован при каждом запуске программы. таким образом хакеры больше не могут злоупотреблять переполнением буфера и вычислять адреса возврата там, где они внедряют свой собственный код.

Так что нет, вы не можете сказать ничего общего об адресах переменных. Вы должны проверить свой компилятор и операционную систему.

person Serve Laurijssen    schedule 27.01.2017

Вы можете привести int к указателю. Это стандарт. И стандарт гарантирует, что если вы вернете указатель обратно к int, вы должны получить исходное значение, при условии, что между ними не произошло усечения.

Но разыменование такого указателя является Undefined Behavior. Это означает, что в обычной реализации вы либо получите ошибку сегмента или нарушение памяти, если попытаетесь прочитать неотображенный адрес или адрес только для записи, либо просто получите непредсказуемое значение, потому что вы читаете в месте, которое вы не знаете, что там лежит. .

И еще хуже, если вы пишете туда, потому что вы можете перезаписать случайное место в вашей программе. Только представьте, что может произойти, если вы перезапишете адрес возврата из функции...

Единственный реальный вариант использования для этого - это когда некоторые специальные аппаратные регистры отображаются на хорошо известные адреса. Тогда вы действительно пишете:

char *p;
p = 0x60;    // say 0x60 is the address of a special register you want to read
char reg_value = *p;

Это не может быть определено стандартом, потому что стандарт не делает предположений о базовой платформе, а является локальным расширением, которое должно быть задокументировано для конкретной аппаратной платформы.

person Serge Ballesta    schedule 27.01.2017

Возьмем аналогию....

int  x = 3;
int* x_p = &x;

Вы можете видеть это так: теперь у вас есть лист бумаги с именем x, на котором написано число 3. И у вас есть еще один лист бумаги, на котором написано местоположение первого листа бумаги. Как это делается в деталях, на самом деле не имеет значения, поэтому предположим, что второй лист бумаги называется x_p и на нем написано x. В этом плане

int y = *x_p;

означает: посмотрите на бумагу x_p, интерпретируйте это как расположение другого листа бумаги и возьмите значение, написанное на этом листе, то есть y будет иметь значение 3.

Что произойдет, если вы напишете что-то еще на бумаге x_p? Во-первых, если вы все-таки попытаетесь интерпретировать его как местонахождение другой бумажки, у вас ничего не получится, либо вы просто получите случайную бумажку, но не ту, которую искали. Во-вторых, как это повлияет на первый лист бумаги x? Это никак не влияет на это. Указатели — это такие же переменные, как и любые другие, просто вы обычно интерпретируете их значение как местоположение какой-либо другой переменной, но в остальном между указателем и указателем нет никакой связи.

Не лучшая аналогия, но, возможно, это поможет.

person 463035818_is_not_a_number    schedule 27.01.2017

Да, вы правы, и это адресный оператор. Он дает указатель, на какой адрес он будет указывать.

Если вы набираете p со звездочкой, это означает, что вы хотите получить значение из этой точки указателя.

Целое число занимает 4 байта из памяти, поэтому каждый раз, когда вы создаете целое число, оно будет занимать 4 байта из стека памяти. Это может быть убывающий или возрастающий порядок.

Вы также можете попробовать все это на своей стороне.

int n = 23,*p;
int n2 = 24;
int n3 = 25;

p = &n;
printf("%d %d %d %d %d",p,n,*p,&n2,&n3);
person hunterTR    schedule 27.01.2017
comment
%d ожидает int. Передача int * имеет неопределенное поведение. Кроме того, int не обязательно должен занимать 4 байта. - person melpomene; 27.01.2017
comment
извините, я не хотел иметь в виду, что каждый int занимает 4 байта. Это был пример 32-битного целого числа по умолчанию. - person hunterTR; 30.01.2017