Подтвердить что ты не робот

Насколько приемлемо думать о указателях С++ как адресах памяти?

Когда вы изучаете С++ или, по крайней мере, когда я узнал его с помощью С++ Primer, указатели назывались "адресами памяти" элементов, на которые они указывают. Мне интересно, насколько это верно.

Например, если два элемента *p1 и *p2 имеют свойство p2 = p1 + 1 или p1 = p2 + 1 тогда и только тогда, когда они смежны в физической памяти?

4b9b3361

Ответ 1

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

Что касается вашего окончательного утверждения, вы не можете сделать это предположение даже в адресном пространстве виртуальной памяти. Арифметика указателя действительна только в блоках смежной памяти, таких как массивы. И хотя допустимо (в C и С++) назначать указатель на одну точку после массива (или скалярного), поведение при отсрочке такого указателя составляет undefined. Гипотеза о смежности в физической памяти в контексте C и С++ бессмысленна.

Ответ 2

Совсем нет.

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

Указатели - указатели. Они указывают на вещи. Будут ли они реализованы как адреса памяти в действительности? Может быть. Они также могут быть оптимизированы или (в случае, например, для указателей-членов), они могут быть несколько более сложными, чем простой числовой адрес.

Когда вы начинаете думать о указателях как целые числа, которые сопоставляются с адресами в памяти, вы начинаете забывать, например, что undefined удерживать указатель на объект, который не существует (вы не можете просто увеличивать и уменьшать указатель willy nilly на любой адрес памяти, который вам нравится).

Ответ 3

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

* p1 и * p2 имеют свойство p2 = p1 + 1 или p1 = p2 + 1 тогда и только тогда, когда они смежны в физической памяти

является правильным только в том случае, если p1 и p2 имеют один и тот же тип или указывают на типы того же размера.

Ответ 4

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

Однако компилятор делает интересную магию, чтобы помочь вам в том, что нормальные адреса памяти [во всех современных современных процессорах по крайней мере] являются байтовыми адресами, а объект, на который ссылается ваш указатель, может быть не совсем одним байтом, Поэтому, если у нас T* ptr;, ptr++ будет делать ((char*)ptr) + sizeof(T); или ptr + n is ((char*)ptr) + n*sizeof(T). Это также означает, что для вашего p1 == p2 + 1 требуется p1 и p2 быть одного типа T, так как +1 на самом деле +sizeof(T)*1.

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

Ответ 5

Операционная система предоставляет абстракцию физической машины вашей программе (т.е. ваша программа работает на виртуальной машине). Таким образом, ваша программа не имеет доступа к физическому ресурсу вашего компьютера, будь то время CPU, память и т.д.; он просто должен попросить ОС для этих ресурсов.

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

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

В нижней строке указано, что указатели являются адресами памяти. Тем не менее, они являются адресами в пространстве виртуальной памяти, и именно операционная система может решить, как это сопоставляется с физическим пространством памяти.

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

Ответ 6

Как и другие переменные, указатель хранит данные, которые могут быть адресом памяти, где хранятся другие данные.

Итак, указатель - это переменная, которая имеет адрес и может содержать адрес.

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


Относительно вашего второго вопроса:

Арифметика указателя действительна для непрерывного фрагмента памяти. Если p2 = p1 + 1 и оба указателя имеют один и тот же тип, то p1 и p2 указывает на непрерывный кусок памяти. Таким образом, адреса p1 и p2 находятся рядом друг с другом.

Ответ 7

Я думаю, этот ответ имеет правильную идею, но плохая терминология. То, что C-указатели предоставляют, - это полная противоположность абстракции.

Абстракция представляет собой ментальную модель, которую относительно легко понять и обосновать, даже если аппаратное обеспечение более сложное и трудное для понимания или сложнее рассуждать.

Указатели C противоположны этому. Они учитывают возможные трудности аппаратного обеспечения, даже если реальное оборудование часто проще и проще рассуждать. Они ограничивают ваше рассуждение тем, что допускается объединением самых сложных частей самого сложного оборудования, независимо от того, насколько просто может быть на самом деле имеющееся оборудование.

Указатели С++ добавляют одну вещь, которую C не включает. Он позволяет сравнивать все указатели того же типа для заказа, даже если они не находятся в одном массиве. Это позволяет немного больше ментальной модели, даже если она не соответствует аппаратным средствам.

Ответ 8

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

Ответ 9

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

Фактически вы можете проверить это, напечатав фактическое число, сохраненное на них, с помощью printf().

Остерегайтесь, однако, что type * операции приращения/уменьшения указателя выполняются с помощью sizeof(type). Посмотрите сами с этим кодом (он тестируется онлайн на Repl.it):

#include <stdio.h>

int main() {
    volatile int i1 = 1337;
    volatile int i2 = 31337;
    volatile double d1 = 1.337;
    volatile double d2 = 31.337;
    volatile int* pi = &i1;
    volatile double* pd = &d1;
    printf("ints: %d, %d\ndoubles: %f, %f\n", i1, i2, d1, d2);
    printf("0x%X = %d\n", pi, *pi);
    printf("0x%X = %d\n", pi-1, *(pi-1));
    printf("Difference: %d\n",(long)(pi)-(long)(pi-1));
    printf("0x%X = %f\n", pd, *pd);
    printf("0x%X = %f\n", pd-1, *(pd-1));
    printf("Difference: %d\n",(long)(pd)-(long)(pd-1));
}

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

Выход был:

ints: 1337, 31337
doubles: 1.337000, 31.337000
0xFAFF465C = 1337
0xFAFF4658 = 31337
Difference: 4
0xFAFF4650 = 1.337000
0xFAFF4648 = 31.337000
Difference: 8

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

Также обратите внимание, что & и * являются действительными операторами для ссылки ( "получить адрес памяти этой переменной" ) и разыменования ( "получить содержимое этого адреса памяти" ).

Это также можно использовать для таких трюков, как получение двоичных значений IEEE 754 для float, путем литья float* как int*:

#include <iostream>

int main() {
    float f = -9.5;
    int* p = (int*)&f;

    std::cout << "Binary contents:\n";
    int i = sizeof(f)*8;
    while(i) {
        i--;
        std::cout << ((*p & (1 << i))?1:0);
   } 
}

Результат:

Binary contents:
11000001000110000000000000000000 

Пример, взятый из https://pt.wikipedia.org/wiki/IEEE_754. Проверьте на любом преобразователе.

Ответ 10

Указатели - это адреса памяти, но вы не должны предполагать, что они отражают физический адрес. Когда вы видите такие адреса, как 0x00ffb500, это логические адреса, которые MMU переведет на соответствующий физический адрес. Это наиболее вероятный сценарий, поскольку виртуальная память является самой расширенной системой управления памятью, но могут быть системы, которые непосредственно управляют физическим адресом.

Ответ 11

Конкретный пример, который вы даете:

Например, если два элемента * p1 и * p2 имеют свойство p2 = p1 + 1 или p1 = p2 + 1 тогда и только тогда, когда они смежны в физической памяти?

выйдет из строя на платформах, у которых нет плоского адресного пространства, например PIC. Чтобы получить доступ к физической памяти в ПОС, вам нужны как адрес, так и номер банка, но последний может быть получен из внешней информации, такой как конкретный исходный файл. Таким образом, выполнение арифметики по указателям из разных банков дало бы неожиданные результаты.

Ответ 12

В соответствии со стандартом С++ 14, [expr.unary.op]/3:

Результат унарного оператора & является указателем на его операнд. Операнд должен быть lvalue или identified-id. Если операнд является квалифицированным идентификатором, именовающим нестатический член m некоторого класса C с типом T, результат имеет тип "указатель на член класса C типа T" и prvalue, обозначающее C::m. В противном случае, если тип выражения равен T, результат имеет тип "указатель на T" и является значением primalue , который является адресом выделенного объекта или указателем на назначенную функцию. [Примечание. В частности, адрес объекта типа "cv T" является "указателем на cv T" , с тем же CV-квалификацией. -end note]

Таким образом, это ясно и однозначно говорит о том, что указатели на тип объекта (т.е. T *, где T не является типом функции) содержат адреса.


"адрес" определяется [intro.memory]/1:

Память, доступная для программы на С++, состоит из одной или нескольких последовательностей смежных байтов. Каждый байт имеет уникальный адрес.

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

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


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

Чтобы избежать написания переносного кода, не следует предполагать, что адрес идентичен целому числу. Правила арифметики для указателей в любом случае отличаются от правил арифметики для целых чисел. Точно так же мы не будем говорить, что 5.0f совпадает с 1084227584, хотя они имеют одинаковые представления бит в памяти (в IEEE754).