Я хотел бы знать архитектуры, которые нарушают допущения, которые я перечислил ниже.
Я вижу, что Стивен С. упомянул машины 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