Я знаю, что указатели содержат адреса. Я знаю, что типы указателей «общеизвестны» на основе «типа» данных, на которые они указывают. Но указатели по-прежнему являются переменными, и адреса, которые они содержат, должны иметь тип данных. Согласно моей информации, адреса в шестнадцатеричном формате. Но я до сих пор не знаю, какой "тип" данных это шестнадцатеричный. (Обратите внимание, что я знаю, что такое шестнадцатеричное число, но когда вы 10CBA20
, например, говорите , является ли эта строка символов «целыми числами», что? Когда я хочу получить доступ к адресу и манипулировать им ... сама, мне нужно знать его тип. вот почему я спрашиваю.)
30
Ответы:
Тип переменной указателя: .. указатель.
Операции, которые вам формально разрешено делать в C, состоят в том, чтобы сравнить их (с другими указателями или специальным значением NULL / ноль), добавить или вычесть целые числа или привести его к другим указателям.
Как только вы примете неопределенное поведение , вы можете посмотреть, каково значение на самом деле. Обычно это машинное слово, то же самое, что и целое число, и обычно оно может быть преобразовано без потерь в целочисленный тип. (Довольно много кода Windows делает это, скрывая указатели в DWORD или HANDLE typedefs).
Есть некоторые архитектуры, где указатели не просты, потому что память не плоская. DOS / 8086 «рядом» и «далеко»; Различная память и кодовые пространства PIC.
источник
p1-p2
. Результатом является целочисленное значение со знаком. В частности,&(array[i])-&(array[j]) == i-j
intptr_t
иuintptr_t
именно оно гарантированно будет «достаточно большим» для значений указателя.p
спецификатора к printf делает получение читабельного представления указателя void определенным, если поведение зависит от реализации в c.Вы усложняете вещи.
Адреса просто целые числа, точка. В идеале это номер ячейки памяти, на которую ссылаются (на практике это усложняется из-за сегментов, виртуальной памяти и т. Д.).
Шестнадцатеричный синтаксис - это вымысел, который существует только для удобства программистов. 0x1A и 26 - это одно и то же число абсолютно одинакового типа , и компьютер не использует ни то, ни другое - внутренне компьютер всегда использует 00011010 (последовательность двоичных сигналов).
То, позволяет ли компилятор обрабатывать указатели как числа, зависит от определения языка - языки «системного программирования» традиционно более прозрачны в отношении того, как все работает под капотом, в то время как языки «высокого уровня» чаще пытаются скрыть «голое железо» от программиста - но это ничего не меняет в том факте, что указатели являются числами, и обычно это самый распространенный тип числа (тот, который имеет столько же разрядов, сколько ваша архитектура процессора).
источник
Указатель - это просто указатель. Это не что-то еще. Не пытайтесь думать, что это что-то другое.
В таких языках, как C, C ++ и Objective-C, указатели данных имеют четыре вида возможных значений:
Существуют также указатели на функции, которые либо идентифицируют функцию, либо являются нулевыми указателями на функции, либо имеют неопределенное значение.
Другие указатели являются «указателем на член» в C ++. Это определенно не адреса памяти! Вместо этого они идентифицируют члена любого экземпляра класса. В Objective-C у вас есть селекторы, которые напоминают «указатель на метод экземпляра с заданным именем метода и именами аргументов». Как и указатель на член, он идентифицирует все методы всех классов, если они выглядят одинаково.
Вы можете исследовать, как конкретный компилятор реализует указатели, но это совершенно другой вопрос.
источник
class A { public: int num; int x; }; int A::*pmi = &A::num; A a; int n = a.*pmi;
Переменнаяpmi
была бы бесполезной, если бы она не содержала адрес памяти, а именно, как устанавливает последняя строка кода, адрес членаnum
экземпляраa
классаA
. Вы можете привести это к обычномуint
указателю (хотя компилятор, вероятно, выдаст вам предупреждение) и успешно разыменовать его (доказав, что это синтаксический сахар для любого другого указателя).Указатель - это битовый паттерн, адресующий (однозначно идентифицирующий для чтения или записи) слово памяти в ОЗУ. По историческим и общепринятым причинам единица обновления составляет восемь битов, известные на английском языке как «байт» или на французском, более логично, как октет. Это вездесуще, но не присуще; другие размеры существовали.
Если я правильно помню, был один компьютер, который использовал 29-битное слово; это не только степень двойки, это даже простое число. Я думал, что это был SILLIAC, но соответствующая статья в Википедии не поддерживает это. CAN BUS использует 29-битные адреса, но по договоренности сетевые адреса не называются указателями, даже если они функционально идентичны.
Люди продолжают утверждать, что указатели являются целыми числами. Это не является ни внутренним, ни существенным, но если мы интерпретируем битовые шаблоны как целые числа, появляется полезное качество порядкового номера, позволяющее осуществлять очень прямую (и поэтому эффективную на небольших аппаратных средствах) реализацию таких конструкций, как «строка» и «массив». Понятие непрерывной памяти зависит от порядковой смежности, и взаимное расположение возможно; целочисленное сравнение и арифметические операции могут быть осмысленно применены. По этой причине почти всегда существует сильная корреляция между размером слова для адресации хранилища и ALU (то, что выполняет целочисленную математику).
Иногда эти два не соответствуют. В ранних ПК адресная шина имела ширину 24 бита.
источник
char*
например, для целей копирования / сравнения памяти иsizeof char==1
в соответствии со стандартом C), а не слово (если только размер слова CPU не совпадает с размером байта).По сути, каждый современный компьютер - это немного напористая машина. Обычно он разбрасывает биты в кластерах данных, называемых байтами, словами, словами или словами.
Байт состоит из 8 битов, слова 2 байта (или 16 битов), слова 2 слова (или 32 бита) и слова 2 слова (или 64 бита). Это не единственный способ расставить биты. 128-битные и 256-битные манипуляции также происходят, часто в инструкциях SIMD.
Инструкции по сборке работают с регистрами, а адреса памяти обычно работают в одной из вышеуказанных форм.
ALU (арифметико-логические единицы) работают с такими блоками битов, как если бы они представляли целые числа (обычно формат дополнения Two), а FPU - как со значениями с плавающей запятой (обычно в стиле IEEE 754
float
иdouble
). Другие части будут действовать так, как если бы они были связаны данными некоторого формата, символов, записей таблицы, инструкций процессора или адресов.На типичном 64-битном компьютере пакеты по 8 байт (64 бита) являются адресами. Мы отображаем эти адреса условно в шестнадцатеричном формате (например
0xabcd1234cdef5678
), но для людей это простой способ чтения битовых комбинаций. Каждый байт (8 бит) записывается в виде двух шестнадцатеричных символов (эквивалентно, каждый шестнадцатеричный символ - от 0 до F - представляет 4 бита).Что на самом деле происходит (для некоторого уровня на самом деле), так это то, что есть биты, которые обычно хранятся в регистре или хранятся в соседних местах в банке памяти, и мы просто пытаемся описать их другому человеку.
Следующий указатель состоит из запроса контроллера памяти предоставить нам некоторые данные в этом месте. Как правило, вы запрашиваете у контроллера памяти определенное количество байтов в определенном месте (ну, неявно, диапазон ячеек, обычно смежных), и оно доставляется через различные механизмы, в которые я не пойду.
Код обычно указывает место назначения для данных, которые будут извлечены - регистр, другой адрес памяти и т. Д. - и обычно плохая идея загружать данные с плавающей запятой в регистр, ожидающий целое число, или наоборот.
Тип данных в C / C ++ - это то, что компилятор отслеживает, и это меняет то, какой код генерируется. Обычно в данных нет ничего, что делало бы их фактически одного типа. Просто набор битов (сгруппированных в байты), которыми код манипулирует целочисленным образом (или плавающим, или адресным способом).
Есть исключения из этого. Есть архитектуры , где определенные вещи , которые другой вид бит. Наиболее распространенным примером являются защищенные страницы выполнения - хотя инструкции, указывающие процессору, что это биты, во время выполнения страницы (памяти), содержащие код для выполнения, помечаются специально, не могут быть изменены, и вы не можете выполнять страницы, которые не отмечены как страницы исполнения.
Есть также данные только для чтения (иногда хранящиеся в ПЗУ, которые физически не могут быть записаны в!), Проблемы с выравниванием (некоторые процессоры не могут загрузить
double
s из памяти, если они не выровнены определенным образом, или инструкции SIMD, требующие определенного выравнивания), и множество другие особенности архитектурыДаже вышеупомянутый уровень детализации является ложью. Компьютеры "не" толкаются вокруг битов, они действительно толкают напряжение и ток. Эти напряжения и ток иногда не делают того, что они «должны» делать на уровне абстракции битов. Микросхемы предназначены для обнаружения большинства таких ошибок и их исправления, при этом абстракция более высокого уровня не должна знать об этом.
Даже это ложь.
Каждый уровень абстракции скрывает нижеприведенный уровень и позволяет вам думать о решении проблем без необходимости учитывать диаграммы Фейнмана для распечатки
"Hello World"
.Таким образом, на достаточном уровне честности компьютеры выдвигают биты, и эти биты получают значение в зависимости от того, как они используются.
источник
Люди много думают о том, являются ли указатели целыми числами или нет. На самом деле есть ответы на эти вопросы. Тем не менее, вам придется сделать шаг в страну спецификаций, которая не для слабонервных. Мы собираемся взглянуть на спецификацию C, ISO / IEC 9899: TC2
6.3.2.3. Указатели
Целое число может быть преобразовано в любой тип указателя. За исключением случаев, указанных ранее, результат определяется реализацией, может быть неправильно выровнен, может не указывать на объект ссылочного типа и может быть представлением прерывания.
Любой тип указателя может быть преобразован в целочисленный тип. За исключением случаев, указанных ранее, результат определяется реализацией. Если результат не может быть представлен в целочисленном типе, поведение не определено. Результат не обязательно должен находиться в диапазоне значений любого целочисленного типа.
Теперь для этого вам нужно знать некоторые общие термины спецификации. «определенная реализация» означает, что каждый отдельный компилятор может определять его по-своему. Фактически, компилятор может даже определять его по-разному в зависимости от ваших настроек компилятора. Неопределенное поведение означает, что компилятору разрешено делать абсолютно все, от ошибки времени компиляции до необъяснимого поведения, и до идеальной работы.
Из этого мы можем видеть, что основная форма хранения не указана, кроме того, что может быть преобразование в целочисленный тип. Теперь, по правде говоря, практически каждый компилятор под солнцем представляет указатели под капотом как целочисленные адреса (с небольшим количеством особых случаев, когда он может быть представлен как 2 целых числа вместо 1), но спецификация допускает абсолютно все, например, представление адреса в виде строки из 10 символов!
Если мы переместимся вперед из C и посмотрим на спецификацию C ++, мы получим немного больше ясности
reinterpret_cast
, но это другой язык, поэтому его значение для вас может отличаться:ISO / IEC N337: проект спецификации C ++ 11 (у меня есть только проект под рукой)
5.2.10 Переосмыслить приведение
Указатель может быть явно преобразован в любой целочисленный тип, достаточно большой для его хранения. Функция отображения определяется реализацией. [Примечание: предполагается, что это не удивительно для тех, кто знает структуру адресации базовой машины. - примечание] Значение типа std :: nullptr_t может быть преобразовано в целочисленный тип; преобразование имеет то же значение и действительность, что и преобразование (void *) 0 в целочисленный тип. [Примечание: reinterpret_cast нельзя использовать для преобразования значения любого типа в тип std :: nullptr_t. —Конечная записка]
Значение целочисленного типа или типа перечисления может быть явно преобразовано в указатель. Указатель, преобразованный в целое число достаточного размера (если таковое существует в реализации) и обратно в тот же тип указателя, будет иметь свое первоначальное значение; Отображения между указателями и целыми числами определяются реализацией. [Примечание: за исключением случаев, описанных в 3.7.4.3, результатом такого преобразования не будет безопасно полученное значение указателя. —Конечная записка]
Как вы можете видеть здесь, спустя еще несколько лет, C ++ обнаружил, что можно с уверенностью предположить, что сопоставление с целыми числами существует, поэтому больше нет разговоров о неопределенном поведении (хотя есть интересное противоречие между частями 4 и 5 с формулировкой «если таковая существует в реализации»)
Теперь, что вы должны от этого отнять?
Лучшая ставка: приведение к (char *). Спецификации C и C ++ полны правил, определяющих упаковку массивов и структур, и оба всегда допускают приведение любого указателя на символ *. char всегда 1 байт (не гарантируется в C, но в C ++ 11 он стал обязательной частью языка, поэтому относительно безопасно предположить, что он везде 1 байт). Это позволяет вам выполнять некоторую арифметику с указателями на уровне байтов, не прибегая к необходимости знать конкретные представления указателей реализации.
источник
char *
? Я имею в виду гипотетическую машину с отдельными адресными пространствами для кода и данных.char
всегда равен 1 байту в C. Цитируя из стандарта C: «Оператор sizeof возвращает размер (в байтах) своего операнда» и «Когда sizeof применяется к операнду с типом char, unsigned char или char со знаком, (или его квалифицированная версия) результат 1 ". Возможно, вы думаете, что байт имеет длину 8 бит. Это не обязательно так. Для соответствия стандарту байт должен содержать не менее 8 бит.На большинстве архитектур тип указателя перестает существовать после того, как он был переведен в машинный код (за исключением, возможно, «жирных указателей»). Следовательно, указатель на a
int
будет неотличим от указателя на adouble
, по крайней мере, сам по себе. *[*] Хотя вы все еще можете делать предположения, основываясь на видах операций, которые вы к нему применяете.
источник
В C и C ++ важно понимать, что это за типы. Все, что они действительно делают, это указывают компилятору, как интерпретировать набор битов / байтов. Давайте начнем со следующего кода:
В зависимости от архитектуры, целому числу обычно дается 32 бита для хранения этого значения. Это означает, что пространство в памяти, где хранится var, будет выглядеть примерно так: «11111111 11111111 11111010 11000111» или в шестнадцатеричном формате «0xFFFFFAC7». Вот и все. Это все, что хранится в этом месте. Все типы делают, это говорят компилятору, как интерпретировать эту информацию. Указатели ничем не отличаются. Если я сделаю что-то вроде этого:
Затем компилятор получит расположение переменной var, а затем сохранит этот адрес таким же образом, как первый фрагмент кода сохраняет значение -1337. Нет разницы в том, как они хранятся, просто в том, как они используются. Не имеет значения, что я сделал var_ptr указателем на int. Если бы вы хотели, вы могли бы сделать.
Это скопирует вышеупомянутое шестнадцатеричное значение переменной var (0xFFFFFAC7) в папку, в которой хранится значение var2. Если бы мы тогда использовали var2, мы нашли бы, что значение было бы 4294965959. Байты в var2 такие же, как var, но числовое значение отличается. Компилятор интерпретировал их по-разному, потому что мы сказали, что эти биты представляют длинную без знака. Вы можете сделать то же самое для значения указателя тоже.
В конечном итоге вы интерпретируете значение, представляющее адрес var, как целое число без знака в этом примере.
Надеюсь, это прояснит вам и даст вам лучшее представление о том, как работает C. Обратите внимание, что вы НЕ ДОЛЖНЫ делать сумасшедшие вещи, которые я делал в двух приведенных ниже строках в реальном производственном коде. Это было просто для демонстрации.
источник
Integer.
Адресное пространство в компьютере нумеруется последовательно, начиная с 0, и увеличивается на 1. Таким образом, указатель будет содержать целое число, соответствующее адресу в адресном пространстве.
источник
Типы сочетаются.
В частности, некоторые типы объединяются, как если бы они были параметризованы с помощью заполнителей. Типы массива и указателя похожи на это; у них есть один такой заполнитель, который является типом элемента массива или указываемой вещи, соответственно. Типы функций также таковы; они могут иметь несколько заполнителей для параметров и заполнитель для возвращаемого типа.
Переменная, которая объявлена для хранения указателя на символ, имеет тип «указатель на символ». Переменная, которая объявлена для хранения указателя на указатель на int, имеет тип «указатель на указатель на int».
Тип (указатель на) указатель на указатель на int может быть изменен на указатель на int с помощью операции разыменования. Таким образом, понятие типа - это не просто слова, а математически значимая конструкция, определяющая, что мы можем делать со значениями типа (такими как разыменование, передача в качестве параметра или присвоение переменной; она также определяет размер (число байтов) операции индексации, арифметики и увеличения / уменьшения).
PS Если вы хотите углубиться в типы, попробуйте этот блог: http://www.goodmath.org/blog/2015/05/13/expressions-and-arity-part-1/
источник