Детали реализации указателя на C

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

  1. sizeof (int *) == sizeof (char *) == sizeof (void *) == sizeof (func_ptr *)

  2. Представление в памяти всех указателей для данной архитектуры одинаково независимо от указанного типа данных.

  3. Представление указателя в памяти совпадает с целым числом той же длины в битах, что и архитектура.

  4. Умножение и деление типов данных указателя запрещено только компилятором. ПРИМЕЧАНИЕ: Да, я знаю, что это бессмысленно. Я имею в виду, есть ли аппаратная поддержка, запрещающая это неправильное использование?

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

  6. Увеличение указателя эквивалентно добавлению sizeof(the pointed data type) к адресу памяти, хранящемуся указателем. Если p - это int32*, тогда p+1 равно адресу памяти через 4 байта после p.

Я больше всего привык к указателям, используемым в непрерывном пространстве виртуальной памяти. Для этого использования я обычно могу думать о них как о адресах в числовой строке. См. Вопрос о переполнении стека Сравнение указателей.


person Will Bickford    schedule 29.08.2009    source источник
comment
Вот пример, который может вас заинтересовать: labs.adobe.com/technologies/alchemy C ++ компилятор не должен ориентироваться на собственный код. Он может нацеливаться на виртуальную машину и генерировать байт-код Flash, как показано по этой ссылке. И если вы генерируете байт-код Flash, ваши указатели определенно не будут необработанными адресами памяти. Это будет то, что лучше всего соответствует формату байт-кода. Внезапно многие бессмысленные ограничения могут стать очень важными.   -  person jalf    schedule 30.08.2009


Ответы (11)


Я не могу привести конкретные примеры всего этого, но я сделаю все, что в моих силах.

sizeof(int *) == sizeof(char *) == sizeof(void *) == sizeof(func_ptr *)

Я не знаю ни одной системы, в которой я знал бы, что это неверно, но учтите:

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

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

Представление в памяти всех указателей для данной архитектуры одинаково независимо от указанного типа данных.

Подумайте об указателях на члены и о обычных указателях. У них разное представление (или размер). Указатель на член состоит из указателя this и смещения.

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

Представление указателя в памяти совпадает с целым числом той же длины в битах, что и архитектура.

Зависит от того, как определена длина в битах. :) int на многих 64-битных платформах по-прежнему 32 бит. Но указатель 64 бита. Как уже было сказано, ЦП с сегментированной моделью памяти будут иметь указатели, состоящие из пары чисел. Точно так же указатели на элементы состоят из пары чисел.

Умножение и деление типов данных указателя запрещено только компилятором.

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

Однако, чтобы ответить, что вы имеете в виду, поищите процессоры Motorola 68000. Я считаю, что у них есть отдельные регистры для целых чисел и адресов памяти. Это означает, что они могут легко запретить такие бессмысленные операции.

Все значения указателя могут быть преобразованы в одно целое число.

Там ты в безопасности. Стандарты C и C ++ гарантируют, что это всегда возможно, независимо от структуры пространства памяти, архитектуры процессора и всего остального. В частности, они гарантируют отображение, определяемое реализацией. Другими словами, вы всегда можете преобразовать указатель в целое число, а затем преобразовать это целое число обратно, чтобы получить исходный указатель. Но языки C / C ++ ничего не говорят о том, каким должно быть промежуточное целочисленное значение. Это зависит от конкретного компилятора и оборудования, на которое он нацелен.

Увеличение указателя эквивалентно добавлению sizeof (указанного типа данных) к адресу памяти, хранящемуся указателем.

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

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

Наконец, как я уже упоминал в комментарии к вашему вопросу, имейте в виду, что C ++ - это просто язык. Его не волнует, для какой архитектуры он скомпилирован. Многие из этих ограничений могут показаться неясными на современных процессорах. Но что, если вы ориентируетесь на процессоры прошлых лет? Что, если вы нацеливаетесь на процессоры следующего десятилетия? Вы даже не знаете, как они будут работать, поэтому не можете много о них предполагать. Что, если вы нацеливаетесь на виртуальную машину? Уже существуют компиляторы, которые генерируют байт-код для Flash, готовый к запуску с веб-сайта. Что, если вы хотите скомпилировать исходный код C ++ в Python?

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

person jalf    schedule 30.08.2009

Я не имею в виду конкретные примеры из реального мира, но «авторитет» - это стандарт C. Если что-то не требуется стандартом, вы можете создать соответствующую реализацию, которая намеренно не соответствует любым другим предположениям. Некоторые из этих предположений верны в большинстве случаев просто потому, что удобно реализовать указатель как целое число, представляющее адрес памяти, который может быть напрямую получен процессором, но это просто следствие «удобства» и не может рассматриваться как универсальная правда.

  1. Не требуется стандартом (см. Этот вопрос < / а>). Например, sizeof(int*) может быть не равно size(double*). void* гарантированно сможет хранить любое значение указателя.
  2. Стандарт не требует. По определению, размер - это часть представления. Если размер может быть другим, то и представление может быть другим.
  3. Не обязательно. Фактически, «битовая длина архитектуры» - расплывчатое утверждение. Что такое 64-битный процессор на самом деле? Это адресная шина? Размер регистров? Шина данных? Какие?
  4. Нет смысла «умножать» или «делить» указатель. Это запрещено компилятором, но вы, конечно, можете умножать или делить базовое представление (что на самом деле не имеет для меня смысла), что приводит к неопределенному поведению.
  5. Может быть, я не понимаю, что вы думаете, но все в цифровом компьютере - это просто какое-то двоичное число.
  6. Да; вроде. Гарантированно указывает на место, которое sizeof(pointer_type) дальше. Это не обязательно эквивалентно арифметическому сложению числа (т.е. дальше здесь является логической концепцией. Фактическое представление зависит от архитектуры)
person mmx    schedule 29.08.2009
comment
У вас есть ссылки, поддерживающие №1 и №2? Мне искренне интересно понять, почему это так. №3 не должен быть расплывчатым - какой бы размер памяти ни потребовался для данной архитектуры. # 4 Я понимаю, что нет смысла умножать указатели. Я поясню это в вопросе. # 5 Я имею в виду одно число, которое не состоит из более чем одной «вещи». - person Will Bickford; 30.08.2009
comment
Will: # 1, и, следовательно, # 2 покрывается другим вопросом SO, с которым я связался. В ответе есть цитата из стандартов. Re # 3: то, что вы говорите, - это размер адресной шины, но не все процессоры имеют одну адресную шину / память. (Гипотетический) процессор может целочисленные данные в одном ОЗУ и данные с плавающей запятой в другом. # 5: Ну, вы можете добавить эти два раздела и вставить одну вещь, которая все равно будет числом. Я не совсем уверен в том, что вы здесь говорите, поэтому я предпочитаю оставаться в безопасности и не комментировать № 5. - person mmx; 30.08.2009
comment
Единственные известные мне архитектуры для №3 - это архитектуры с отдельной памятью для инструкций и данных. Я никогда не видел и не слышал ни одного с отдельной памятью для целых чисел и с плавающей запятой. - person Will Bickford; 30.08.2009
comment
Уилл: Конечно, это было гипотетически. Все дело в том, что процессор может быть напрямую подключен к более чем одной памяти (и ширина этих шин памяти может варьироваться). - person mmx; 30.08.2009

Для 6: указатель не обязательно является адресом памяти. См., Например, "The Great Pointer Conspiracy" от Stack Пользователь переполнения jalf:

Да, я использовал слово «адрес» в комментарии выше. Важно понимать, что я имею в виду под этим. Я не имею в виду «адрес памяти, по которому физически хранятся данные», а просто абстрактное «все, что нам нужно, чтобы найти значение. Адрес i может быть любым, но когда он у нас есть, мы всегда можем найти и изменить i ".

И:

Указатель - это не адрес памяти! Я упоминал об этом выше, но давайте повторим еще раз. Указатели обычно реализуются компилятором просто как адреса памяти, да, но это не обязательно ».

person Peter Mortensen    schedule 29.08.2009
comment
Кажется, этот ответ отвечает и на несколько других вопросов. Как бы. - person strager; 30.08.2009
comment
Ссылка не работает, похоже, блог не в сети. - person Jens; 04.02.2018

Дополнительная информация об указателях из стандарта C99:

  • 6.2.5 §27 гарантирует, что void* и char* имеют идентичные представления, то есть они могут использоваться взаимозаменяемо без преобразования, то есть один и тот же адрес обозначается одним и тем же битовым шаблоном (что не обязательно должно быть истинным для других типов указателей)
  • 6.3.2.3 §1 заявляет, что любой указатель на неполный или объектный тип может быть приведен к (и от) void* и обратно, и все еще будет действительным; сюда не входят указатели на функции!
  • 6.3.2.3 §6 утверждает, что void* может быть приведен к целым числам (и из них), а 7.18.1.4 §1 предоставляет соответствующие типы intptr_t и uintptr_t; проблема: эти типы являются необязательными - в стандарте явно упоминается, что не обязательно должен быть достаточно большой целочисленный тип, чтобы на самом деле содержать значение указателя!
person Christoph    schedule 29.08.2009
comment
Спасибо за стандартные ссылки C99. - person Will Bickford; 30.08.2009

sizeof(char*) != sizeof(void(*)(void)? - Не на x86 в режиме 36-битной адресации (поддерживается практически на всех процессорах Intel, начиная с Pentium 1)

«Представление указателя в памяти - то же самое, что и целое число той же длины в битах» - представления в памяти нет ни в одной современной архитектуре; Память с тегами никогда не приживалась и была уже устаревшей до того, как был стандартизован C. На самом деле память даже не хранит целые числа, только биты и, возможно, слова (не байты; большая часть физической памяти не позволяет вам читать только 8 бит).

«Умножение указателей невозможно» - 68000 семей; адресные регистры (те, которые содержат указатели) не поддерживают этот IIRC.

«Все указатели могут быть преобразованы в целые числа» - не на PIC.

«Увеличение T * эквивалентно добавлению sizeof (T) к адресу памяти» - истинно по определению. Также эквивалент &pointer[1].

person MSalters    schedule 01.09.2009

Не знаю, как обстоят дела с другими, но для DOS предположение в № 3 неверно. DOS является 16-битным и использует различные уловки для отображения памяти, объем которой превышает 16 бит.

person Imagist    schedule 29.08.2009

Представление указателя в памяти совпадает с целым числом той же длины в битах, что и архитектура.

Я думаю, что это предположение неверно, потому что, например, на 80186 32-битный указатель хранится в двух регистрах (регистр смещения и регистр сегмента), и какая полуслова идет в какой регистр имеет значение во время доступа.

Умножение и деление типов данных указателя запрещено только компилятором.

Вы не можете умножать или делить типы. ;П

Я не уверен, зачем вам умножать или делить указатель.

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

Стандарт C99 позволяет хранить указатели в intptr_t, который является целочисленным типом. Так да.

Увеличение указателя эквивалентно добавлению sizeof (указанного типа данных) к адресу памяти, хранящемуся указателем. Если p - это int32 *, то p + 1 равно адресу памяти через 4 байта после p.

x + y, где x - это T *, а y - целое число, насколько мне известно, эквивалентно (T *)((intptr_t)x + y * sizeof(T)). Выравнивание может быть проблемой, но в sizeof может быть добавлено заполнение. Я не совсем уверен.

person strager    schedule 29.08.2009
comment
80186 все еще где-нибудь используется? Вопрос об умножении в основном теоретический: у меня нет варианта его использования. - person Will Bickford; 30.08.2009
comment
stager: По поводу последнего пункта. Вы не можете выполнять арифметические действия с указателями void* указателей. - person mmx; 30.08.2009

В общем, ответ на все вопросы - «да», потому что только те машины, которые реализуют популярные языки напрямую, увидели свет и сохранились до нынешнего века. Хотя языковые стандарты оставляют за собой право изменять эти «инварианты» или утверждения, этого никогда не происходило в реальных продуктах, за возможным исключением пунктов 3 и 4, которые требуют некоторого повторения, чтобы быть универсально верными.

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

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

Конкретно:

  1. Представление в памяти всех указателей для данной архитектуры одинаково независимо от указанного типа данных. Верно, за исключением чрезвычайно дурацких прошлых проектов, в которых пытались реализовать защиту не на строго типизированных языках, а на оборудовании.
  2. Представление указателя в памяти совпадает с целым числом той же длины в битах, что и архитектура. Может быть, какой-то интегральный тип точно такой же, см. LP64 против LLP64.
  3. Умножение и деление типов данных указателя запрещено только компилятором. Правильно.
  4. Все значения указателя могут быть преобразованы в одно целое число. Другими словами, в каких архитектурах до сих пор используются сегменты и смещения? Сегодня ничто не использует сегменты и смещения, но C int часто бывает недостаточно большим, вам может понадобиться long или long long для удержания указателя.
  5. Увеличение указателя эквивалентно добавлению sizeof (указанного типа данных) к адресу памяти, хранящемуся указателем. Если p - это int32 *, то p + 1 равно адресу памяти через 4 байта после p. Да.

Интересно отметить, что каждый процессор с архитектурой Intel, то есть каждый PeeCee, содержит тщательно продуманный блок сегментации эпической, легендарной сложности. Однако он фактически отключен. Каждый раз, когда загружается ОС ПК, она устанавливает базы сегментов на 0 и длину сегментов на ~ 0, обнуляя сегменты и давая плоскую модель памяти.

person DigitalRoss    schedule 29.08.2009

В 1950-х, 1960-х и 1970-х годах было много архитектур, ориентированных на слова. Но я не могу вспомнить какие-либо основные примеры, в которых использовался компилятор C. Я вспоминаю машины ICL / Three Rivers PERQ в 1980-х годах, которые были адресованы по словам и имели возможность записи контрольный магазин (микрокод). Один из его экземпляров содержал компилятор C и разновидность Unix под названием PNX, но Компилятору C требовался специальный микрокод.

Основная проблема заключается в том, что типы char * на машинах с адресной адресацией по словам неудобны, независимо от того, как вы их реализуете. Вы часто встречаетесь с sizeof(int *) != sizeof(char *) ...

Интересно, что до C существовал язык под названием BCPL, в котором основным типом указателя был адрес слова. ; то есть увеличение указателя дает вам адрес следующего слова, а ptr!1 дает вам слово в ptr + 1. Для адресации байта был другой оператор: ptr%42, если я помню.

person Stephen C    schedule 01.09.2009

РЕДАКТИРОВАТЬ: не отвечайте на вопросы, когда у вас низкий уровень сахара в крови. Ваш мозг (конечно, мой) работает не так, как вы ожидаете. :-(

<удар> Незначительная придирка:

p - это int32 *, тогда p + 1

неверно, он должен быть беззнаковым int32, иначе он будет перенесен на 2 ГБ.

Интересная странность - я получил это от автора компилятора C для микросхемы Transputer - он сказал мне, что для этого компилятора NULL был определен как -2 ГБ. Почему? Поскольку у Transputer был подписанный диапазон адресов: от -2 ГБ до + 2 ГБ. Вы можете в это поверить? Удивительно, не правда ли?

С тех пор я встречал разных людей, которые говорили мне, что такое определение NULL нарушено. Я согласен, но если вы этого не сделаете, вы получите указатели NULL в середине вашего диапазона адресов.

Я думаю, что большинство из нас может быть рад, что мы не работаем над Transputers!

person Stephen Kellett    schedule 16.03.2010
comment
int32* означает указатель на int32. OP ничего не сказал о внутреннем представлении адреса. Кроме того, вся концепция подписанного / беззнакового бессмысленна для адресов, поскольку они не должны использоваться как числа. Разница между адресами - это число, которое будет правильным независимо от того, считаете ли вы адреса подписанными или неподписанными. На самом деле, думать об адресах как о подписанных или неподписанных - это просто произвольное соглашение. Конечно, мы говорим о двух дополнительных арифметических архитектурах. - person slacker; 20.03.2010
comment
Извините, я не совсем ясно соображаю. Бездельник, да ты прав. Я объединил указатель с типом, а не думал об арифметике указателя. Я так устал в данный момент, не осознавал, что я так устал, и я не спал всего час. :-( - person Stephen Kellett; 21.03.2010

Я хотел бы знать архитектуры, которые нарушают допущения, которые я перечислил ниже.

Я вижу, что Стивен С. упомянул машины PERQ, а MSalters упомянул 68000 и PIC.

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

sizeof (int *) == sizeof (char *) == sizeof (void *) == sizeof (func_ptr *)?

Не обязательно. Некоторые примеры:

Большинство компиляторов для 8-битных процессоров с гарвардской архитектурой - PIC и 8051 и M8C - делают sizeof (int *) == sizeof (char *), но отличным от sizeof (func_ptr *).

Некоторые из очень маленьких чипов в этих семействах имеют 256 байт ОЗУ (или меньше), но несколько килобайт PROGMEM (Flash или ROM), поэтому компиляторы часто делают sizeof (int *) == sizeof (char *) равным 1 (a один 8-битный байт), но sizeof (func_ptr *) равен 2 (два 8-битных байта).

Компиляторы для многих более крупных микросхем этих семейств с несколькими килобайтами ОЗУ и 128 или около того килобайтами PROGMEM делают sizeof (int *) == sizeof (char *) равным 2 (два 8-битных байта), но sizeof ( func_ptr *) равный 3 (три 8-битных байта).

Некоторые микросхемы с гарвардской архитектурой могут хранить ровно 2 ^ 16 («64 КБайт») PROGMEM (флэш-памяти или ПЗУ) и еще 2 ^ 16 («64 КБайт») ОЗУ + ввод-вывод с отображением в память. Компиляторы для такой микросхемы делают sizeof (func_ptr *) всегда равным 2 (два байта); но часто есть способ превратить другие виды указателей sizeof (int *) == sizeof (char *) == sizeof (void *) в "long ptr" 3-байтовый универсальный указатель, который имеет дополнительный магический бит, указывающий, указывает ли этот указатель на RAM или PROGMEM. (Это тот тип указателя, который вам нужно передать функции print_text_to_the_LCD (), когда вы вызываете эту функцию из множества различных подпрограмм, иногда с адресом переменной строки в буфере, которая может быть где угодно в ОЗУ, а иногда с одним множества постоянных строк, которые могут быть где угодно в PROGMEM). Такие компиляторы часто имеют специальные ключевые слова («короткий» или «рядом», «длинный» или «далеко»), позволяющие программистам конкретно указывать три разных типа указателей на символы в одной программе - константные строки, которым требуется всего 2 байта, чтобы указать, где в PROGMEM они расположены, непостоянные строки, которым требуется всего 2 байта, чтобы указать, где в ОЗУ они расположены, и вид 3-байтовых указателей, которые принимает "print_text_to_the_LCD ()".

Большинство компьютеров, построенных в 1950-х и 1960-х годах, используют 36-битную длину слова или 18-битное слово с 18-битной (или меньше) адресной шиной. Я слышал, что компиляторы C для таких компьютеров часто используют 9-битные байты с sizeof (int *) == sizeof (func_ptr *) = 2, что дает 18 бит, поскольку все целые числа и функции должны быть выровнены по словам; но sizeof (char *) == sizeof (void *) == 4, чтобы воспользоваться преимуществами специального PDP- 10 инструкций, которые хранят такие указатели в виде полного 36-битного слова. Это полное 36-битное слово включает 18-битный адрес слова и еще несколько битов в других 18-битах, которые (среди прочего) указывают битовую позицию указанного символа в этом слове.

Представление в памяти всех указателей для данной архитектуры одинаково независимо от типа данных, на который указывает?

Не обязательно. Некоторые примеры:

На любой из упомянутых выше архитектур указатели бывают разных размеров. Так как же они могли иметь «одинаковое» представление?

Некоторые компиляторы в некоторых системах используют «дескрипторы» для реализации указателей на символы и других видов указателей. Такой дескриптор отличается для указателя, указывающего на первый «char» в «char big_array[4000]», чем для указателя, указывающего на первый «char» в «char small_array[10]», которые, возможно, являются разными типами данных. , даже когда маленький массив начинается в том же месте в памяти, которое ранее занимал большой массив. Дескрипторы позволяют таким машинам перехватывать и перехватывать переполнение буфера, которое вызывает такие проблемы на других машинах.

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

Представление указателя в памяти совпадает с целым числом той же длины в битах, что и архитектура?

Не обязательно. Некоторые примеры:

В машинах с «тегированная архитектура» каждое слово памяти имеет несколько битов, которые указывают, является ли это слово целое число, или указатель, или что-то еще. С такими машинами, глядя на биты тегов, можно узнать, было ли это слово целым числом или указателем.

Я слышал, что у миникомпьютеров Nova есть «бит косвенного обращения» в каждом слове, которое вдохновило "непрямой многопоточный код". Похоже, что сохранение целого числа очищает этот бит, а сохранение указателя устанавливает этот бит.

Умножение и деление типов данных указателя запрещено только компилятором. ПРИМЕЧАНИЕ: Да, я знаю, что это бессмысленно. Я имею в виду, есть ли аппаратная поддержка, запрещающая это неправильное использование?

Да, некоторое оборудование напрямую не поддерживает такие операции.

Как уже упоминалось, инструкция «умножения» в 68000 и 6809 работает только с (некоторыми) «регистрами данных»; они не могут быть напрямую применены к значениям в «адресных регистрах». (Компилятору было бы довольно легко обойти такие ограничения - переместить эти значения из адресного регистра в соответствующий регистр данных, а затем использовать MUL).

Все значения указателя можно привести к одному типу данных?

да.

Чтобы memcpy () работала правильно, стандарт C требует, чтобы каждое значение указателя любого типа могло быть преобразовано в указатель void ("void *").

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

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

Я не уверен.

Я подозреваю, что все значения указателя могут быть приведены к интегральным типам данных size_t и ptrdiff_t, определенным в <stddef.h>.

Увеличение указателя эквивалентно добавлению sizeof (указанного типа данных) к адресу памяти, хранящемуся указателем. Если p - это int32 *, то p + 1 равно адресу памяти через 4 байта после p.

Непонятно, о чем вы здесь спрашиваете.

В: Если у меня есть массив какой-то структуры или примитивного типа данных (например, «#include <stdint.h> ... int32_t example_array[1000]; ...»), и я увеличиваю указатель, указывающий на этот массив (например, «int32_t p = & example_array [99];. .. p ++; ... "), указывает ли теперь указатель на самый следующий последовательный член этого массива, который является размером sizeof (указанный тип данных) байтов дальше в памяти?

A: Да, компилятор должен заставить указатель после однократного увеличения указывать на следующий независимый последовательный int32_t в массиве sizeof (указанный тип данных) байтов дальше в памяти, чтобы соответствовать стандартам.

В: Итак, если p - это int32 *, то p + 1 равно адресу памяти через 4 байта после p?

A: Когда sizeof (int32_t) фактически равно 4, да. В противном случае, например, для некоторых машин с адресацией по словам, включая некоторые современные DSP, где sizeof (int32_t) может равняться 2 или даже 1, тогда p + 1 равно адресу памяти 2 или даже 1 "C байтам" после p.

Q: Итак, если я возьму указатель и переведу его в int ...

A: Один из видов «ереси всего мира VAX».

Q: ... а затем преобразовать это "int" обратно в указатель ...

A: Другой тип «ереси всего мира - VAX».

В: Итак, если я возьму указатель p, который является указателем на int32_t, и приведу его к некоторому целочисленному типу, который достаточно велик, чтобы содержать указатель, а затем добавлю sizeof( int32_t ) к этому целочисленному типу, а затем позже приведу этот интегральный тип обратно в указатель - когда я все это сделаю, результирующий указатель будет равен p + 1?

Не обязательно.

Многие DSP и несколько других современных микросхем имеют адресно-ориентированную адресацию, а не байтовую обработку, используемую 8-битными микросхемами.

Некоторые компиляторы C для таких микросхем втискивают 2 символа в каждое слово, но для хранения int32_t требуется 2 таких слова, поэтому они сообщают, что sizeof( int32_t ) равно 4. (До меня доходили слухи, что существует компилятор C для 24-битный Motorola 56000, который это делает).

Компилятор должен организовать вещи так, чтобы выполнение «p ++» с указателем на int32_t увеличивало указатель на следующее значение int32_t. У компилятора есть несколько способов сделать это.

Один из стандартных способов - сохранить каждый указатель на int32_t как «собственный адрес слова». Поскольку для хранения одного значения int32_t требуется 2 слова, компилятор C компилирует "int32_t * p; ... p++" в некоторый язык ассемблера, который увеличивает значение этого указателя на 2. С другой стороны, если он делает "int32_t * p; ... int x = (int)p; x += sizeof( int32_t ); p = (int32_t *)x;", этот компилятор C для 56000 скорее всего, скомпилирует его на язык ассемблера, увеличив значение указателя на 4.

Я больше всего привык к указателям, используемым в непрерывном пространстве виртуальной памяти.

Некоторые PIC, 8086 и другие системы имеют несмежную RAM - несколько блоков RAM по адресам, которые «упростили аппаратное обеспечение». С отображаемым в память вводом-выводом или вообще без каких-либо привязок к промежуткам в адресном пространстве между этими блоками.

Это даже более неловко, чем кажется.

В некоторых случаях - например, при использовании оборудования для преобразования битов чтобы избежать проблем, вызванных чтение-изменение-запись - точно такой же бит в ОЗУ может быть прочитанным или записанным с использованием 2 или более разных адресов.

person David Cary    schedule 21.01.2015