Я хотел бы знать архитектуры, которые нарушают предположения, которые я
перечислены ниже.
Я вижу, что Стивен С. упомянул машины 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 (один 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).
Такие компиляторы часто имеют специальные ключевые слова («short» или «near», «long» или «far»), чтобы позволить программистам специально указывать три разных типа указателей на символы в одной и той же программе - строки констант, которым требуется всего 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]
", что, вероятно, различные типы данных, даже если небольшой массив начинается с точно такого же места в памяти, которое ранее занимал большой массив.
Дескрипторы позволяют таким машинам перехватывать и перехватывать переполнения буфера, которые вызывают такие проблемы на других машинах.
"Low-Fat Pointers" , используемые в SAFElite и аналогичных "мягких процессорах", имеют аналогичную "дополнительную информацию" о размере буфера, на который указывает указатель. Указатели с низким содержанием жира обладают тем же преимуществом, что и перехват переполнения буфера.
Представление указателя в памяти совпадает с целым числом
той же длины в битах, что и архитектура?
Не обязательно. Некоторые примеры:
В машинах с «теговой архитектурой» каждое слово в памяти имеет несколько битов, которые указывают, является ли это слово целым числом, или указателем, или чем-то еще.
На таких машинах просмотр битов тега скажет вам, является ли это слово целым числом или указателем.
Я слышал, что миникомпьютеры Nova имеют «бит косвенности» в каждом слове, которое вдохновило «косвенный код с резьбой» . Звучит так, как будто целое число очищает этот бит, а указатель устанавливает этот бит.
Умножение и деление типов данных указателя запрещено только
компилятором. ПРИМЕЧАНИЕ: Да, я знаю, что это бессмысленно. Я имею в виду
- есть ли аппаратная поддержка, чтобы запретить это неправильное использование?
Да, некоторые устройства не поддерживают такие операции напрямую.
Как уже упоминали другие, команда «умножения» в 68000 и 6809 работает только с (некоторыми) «регистрами данных»; их нельзя напрямую применять к значениям в «адресных регистрах».
(Компилятору было бы довольно легко обойти такие ограничения - переместить эти значения из регистра адресов в соответствующий регистр данных и затем использовать MUL).
Все значения указателя могут быть приведены к одному типу данных?
Да.
Для правильной работы memcpy () стандарт C требует, чтобы каждое значение любого указателя можно было привести к пустому указателю ("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 в массиве, размер байтов (указанный тип данных) которого будет дальше в памяти, чтобы соответствовать стандартам.
Q: Итак, если 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".
Q: Итак, если я возьму указатель 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 и другие системы имеют несмежные ОЗУ -
несколько блоков оперативной памяти по адресам, которые «сделали аппаратное обеспечение проще».
С отображенным в память вводом / выводом или вообще без привязки к промежуткам в адресном пространстве между этими блоками.
Это даже более неловко, чем кажется.
В некоторых случаях - например, с помощью оборудования с битовой полосой , используемого для избежания проблем, вызванных чтение-изменение-запись - один и тот же бит в ОЗУ может быть прочитан или написано с использованием 2 или более разных адресов.