В какой степени приемлемо рассматривать указатели C ++ как адреса памяти?

Хотя стандарт C ++ не имеет такого требования, некоторым компиляторам требуется, чтобы все шаблоны функций и классов были доступны в каждой используемой системе переводов. Фактически для этих компиляторов тела шаблонных функций должны быть доступны в файле заголовка. Повторить: это означает, что эти компиляторы не позволят их определять в файлах без заголовка, таких как .cpp-файлы

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

41
задан fuz 29 December 2015 в 14:27
поделиться

8 ответов

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

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

32
ответ дан Bathsheba 29 December 2015 в 14:27
поделиться

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

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

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

11
ответ дан Community 29 December 2015 в 14:27
поделиться

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

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

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

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

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

5
ответ дан Paul92 29 December 2015 в 14:27
поделиться

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

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

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

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

4
ответ дан Jerry Coffin 29 December 2015 в 14:27
поделиться

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

Фактически, вы можете проверить это, напечатав фактическое число, сохраненное на них, с помощью 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* в виде 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 . Проверьте на любом конвертере.

1
ответ дан Ronan Paixão 29 December 2015 в 14:27
поделиться

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

.
0
ответ дан Mr. E 29 December 2015 в 14:27
поделиться

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

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

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

0
ответ дан Owen 29 December 2015 в 14:27
поделиться

Согласно стандарту C ++ 14, [expr.unary.op] / 3:

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

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


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

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

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

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


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

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

0
ответ дан M.M 29 December 2015 в 14:27
поделиться
Другие вопросы по тегам:

Похожие вопросы: