Когда выполняется программа на C, данные сохраняются в куче или стеке. Значения хранятся в адресах RAM. Но как насчет индикаторов типа (например, int
или char
)? Они тоже хранятся?
Рассмотрим следующий код:
char a = 'A';
int x = 4;
Я прочитал, что A и 4 хранятся в адресах RAM здесь. Но как насчет a
и x
? Наиболее запутанно, как выполнение узнает, что a
это char и x
int? Я имею в виду, int
и char
упоминается ли где-нибудь в оперативной памяти?
Допустим, значение хранится где-то в ОЗУ как 10011001; если я являюсь программой, которая выполняет код, как я узнаю, является ли это 10011001 char
или int
?
Что я не понимаю, так это то, как компьютер знает, когда он читает значение переменной с адреса, такого как 10001, является ли он int
или char
. Представьте, что я нажимаю на программу под названием anyprog.exe
. Сразу же начинает выполняться код. Включает ли этот исполняемый файл информацию о том, являются ли хранимые переменные типа int
или char
?
x
такое char, но запускается код печати символов, потому что это то, что выбрал компилятор.Ответы:
Чтобы ответить на вопрос, который вы опубликовали в нескольких комментариях (которые, я думаю, вы должны отредактировать в своем сообщении):
Итак, давайте добавим к нему некоторый код. Допустим, вы пишете:
И давайте предположим, что он хранится в оперативной памяти:
Первая часть - это адрес, вторая часть - значение. Когда ваша программа (которая выполняется как машинный код) запускается, все, что она видит,
0x00010004
это значение0x000000004
. Он не «знает» тип этих данных и не знает, как они «должны» использоваться.Итак, как ваша программа определяет, что нужно делать? Рассмотрим этот код:
У нас есть чтение и запись здесь. Когда ваша программа читает
x
из памяти, она находит0x00000004
там. И ваша программа знает, чтобы добавить0x00000005
к нему. И причина, по которой ваша программа «знает», что это допустимая операция, заключается в том, что компилятор гарантирует, что операция допустима через безопасность типов. Ваш компилятор уже проверил, что вы можете добавить4
и5
вместе. Поэтому, когда ваш двоичный код запускается (exe), он не должен выполнять эту проверку. Он просто выполняет каждый шаг вслепую, предполагая, что все в порядке (плохие вещи случаются, когда они на самом деле, а не в порядке).Другой способ думать об этом, как это. Я даю вам эту информацию:
Тот же формат, что и раньше - адрес слева, значение справа. Какой тип это значение? На данный момент вы знаете столько же информации об этом значении, сколько ваш компьютер знает при выполнении кода. Если бы я сказал вам добавить 12743 к этому значению, вы могли бы это сделать. Вы понятия не имеете, каковы будут последствия этой операции для всей системы, но вы действительно хорошо умеете добавлять два числа, чтобы вы могли это сделать. Это делает значение
int
? Не обязательно - все, что вы видите, это два 32-битных значения и оператор сложения.Возможно, некоторая путаница заключается в том, чтобы вернуть данные обратно. Если мы имеем:
Как компьютер знает, чтобы отобразить
a
в консоли? Ну, есть много шагов к этому. Во-первых, нужно перейти кA
расположению в памяти и прочитать его:Шестнадцатеричное значение для
a
ASCII равно 0x61, поэтому приведенное выше может быть чем-то, что вы увидите в памяти. Так что теперь наш машинный код знает целочисленное значение. Откуда он знает, как превратить целочисленное значение в символ для его отображения? Проще говоря, компилятор позаботился о том, чтобы выполнить все необходимые шаги для этого перехода. Но сам ваш компьютер (или программа / exe) понятия не имеет, что это за тип данных. Это 32-битное значение может быть что угодно -int
,char
, половинаdouble
, указатель, часть массива, частьstring
, часть инструкции и т.д.Вот краткое взаимодействие вашей программы (exe) с компьютером / операционной системой.
Программа: Я хочу начать. Мне нужно 20 МБ памяти.
Операционная система: находит 20 МБ свободной памяти, которые не используются, и передает их
(Важным примечанием является то, что это может вернуть любые 20 МБ свободной памяти, они даже не должны быть смежными. На данный момент программа может работать в той памяти, которая у нее есть, не обращаясь к ОС)
Программа: Я предполагаю, что первое место в памяти - это 32-разрядная целочисленная переменная
x
.(Компилятор гарантирует, что доступ к другим переменным никогда не коснется этого места в памяти. В системе нет ничего, что говорит, что первый байт является переменной
x
, или эта переменнаяx
является целым числом. Аналогия: у вас есть сумка. Вы говорите людям, что вы будете класть в эту сумку только шарики желтого цвета. Когда кто-то потом вытаскивает что-то из сумки, то будет шокирующим, что он вытащит что-то голубое или кубик - что-то пошло не так. То же самое касается компьютеров: ваш Теперь программа предполагает, что первая область памяти - переменная x, и что это целое число. Если что-то еще записано поверх этого байта памяти или предполагается, что это что-то другое - произошло что-то ужасное. Компилятор гарантирует, что такие вещи не не бывает)Программа: сейчас я напишу
2
первые четыре байта, где, как я предполагаю,x
находится.Программа: я хочу добавить 5 к
x
.Читает значение X во временный регистр
Добавляет 5 во временный регистр
Сохраняет значение временного регистра обратно в первый байт, который по-прежнему считается
x
.Программа: я предполагаю, что следующим доступным байтом является переменная char
y
.Программа: я напишу
a
в переменнуюy
.Библиотека используется, чтобы найти значение байта для
a
Байт записывается по адресу, который предполагается программой
y
.Программа: я хочу отобразить содержимое
y
Читает значение во втором месте памяти
Использует библиотеку для преобразования из байта в символ
Использование графических библиотек для изменения экрана консоли (установка пикселей с черного на белый, прокрутка одной строки и т. Д.)
(И это продолжается отсюда)
Вероятно, вы зациклились на том, что происходит, когда первое место в памяти больше не существует
x
? или второго уже нетy
? Что происходит, когда кто-то читаетx
какchar
илиy
как указатель? Короче, плохие вещи случаются. Некоторые из этих вещей имеют четко определенное поведение, а некоторые - неопределенное поведение. Неопределенное поведение - это как раз то, что может произойти все, от чего бы то ни было, до сбоя программы или операционной системы. Даже четко определенное поведение может быть вредоносным. Если я могу изменитьx
указатель на свою программу и заставить вашу программу использовать его в качестве указателя, тогда я смогу заставить вашу программу начать выполнение моей программы - это именно то, что делают хакеры. Компилятор поможет убедиться, что мы не используем егоint x
какstring
и тому подобное. Сам машинный код не знает типов, и он будет делать только то, что в инструкциях сказано. Во время выполнения также обнаруживается большое количество информации: какие байты памяти разрешено использовать программе? Начинаетсяx
с первого байта или 12-го?Но вы можете представить, как ужасно было бы на самом деле писать программы, подобные этой (и вы можете, на языке ассемблера). Вы начинаете с «объявления» своих переменных - вы говорите себе, что байт 1
x
, а байт 2y
, и когда вы пишете каждую строку кода, загружая и сохраняя регистры, вы (как человек) должны помнить, какая из нихx
и какая одинy
, потому что система не имеет ни малейшего представления. И вы (как человек) должны помнить, какие типыx
иy
есть, потому что опять же - система понятия не имеет.источник
Otherwise how can console or text file outputs a character instead of int
Поскольку существует другая последовательность инструкций для вывода содержимого ячейки памяти в виде целого числа или буквенно-цифровых символов. Компилятор знает о типах переменных, выбирает соответствующую последовательность инструкций во время компиляции и записывает ее в EXE.Я думаю, что ваш главный вопрос выглядит так: «Если тип стирается во время компиляции и не сохраняется во время выполнения, то как компьютер узнает, выполнять ли
int
код, который интерпретирует его как код, или выполнять код, который интерпретирует его какchar
? "И ответ ... компьютер не делает. Однако компилятор действительно знает, и это будет просто поставить правильный код в двоичном в первую очередь. Если бы переменная была напечатана как
char
, то компилятор не поместил бы код для обработки ее как aint
в программу, он поместил бы код для обработки ее как achar
.Там являются причины сохраняющего типа во время выполнения:
+
оператора), поэтому по этой причине ему не требуется тип времени выполнения. Однако, опять же, тип времени выполнения в любом случае отличается от статического типа, например, в Java вы можете теоретически стереть статические типы и при этом сохранить тип времени выполнения для полиморфизма. Также обратите внимание, что если вы децентрализуете и специализируете код поиска типов и помещаете его в объект (или класс), то вам также не обязательно нужен тип времени выполнения, например C ++ vtables.Единственная причина для сохранения типа во время выполнения в C заключается в отладке, однако, отладка обычно выполняется с доступным источником, и тогда вы можете просто найти тип в исходном файле.
Тип Erasure вполне нормальный. Это не влияет на безопасность типов: типы проверяются во время компиляции, как только компилятор убедится, что программа безопасна для типов, типы больше не нужны (по этой причине). Это не влияет на статический полиморфизм (он же перегрузка): когда разрешение перегрузки завершено, и компилятор выбрал правильную перегрузку, ему больше не нужны типы. Типы также могут направлять оптимизацию, но, опять же, после того, как оптимизатор выбрал свои оптимизации на основе типов, они больше не нужны.
Сохранение типов во время выполнения требуется только тогда, когда вы хотите что-то сделать с типами во время выполнения.
Haskell - один из самых строгих, самых строгих, типобезопасных статически типизированных языков, и компиляторы Haskell обычно стирают все типы. (Исключением является передача словарей методов для классов типов, я считаю.)
источник
char
в скомпилированный двоичный файл. Он не выводит код дляint
, он не выводит код дляbyte
, он не выводит код для указателя, он просто выводит только код дляchar
. В зависимости от типа не принимаются решения во время выполнения. Вам не нужен тип. Это совершенно и совершенно не имеет значения. Все соответствующие решения уже были приняты во время компиляции.public class JoergsAwesomeNewType {};
Видеть? Я только что изобрел новый тип! Вам нужно купить новый процессор!Компьютер не «знает», что это за адреса, но знает, что есть, в соответствии с инструкциями вашей программы.
Когда вы пишете программу на C, которая пишет и читает переменную char, компилятор создает код ассемблера, который записывает этот фрагмент данных где-то в виде char, и где-то еще есть некоторый другой код, который считывает адрес памяти и интерпретирует его как char. Единственное, что связывает эти две операции - это местоположение адреса в памяти.
Когда приходит время читать, в инструкциях не говорится «посмотрите, какой тип данных есть», а просто говорится что-то вроде «загрузить эту память как число с плавающей запятой». Если адрес для чтения был изменен или что-то перезаписало эту память чем-то, отличным от плавающего числа, то ЦПУ просто в любом случае с радостью загрузит эту память как число с плавающей запятой, и в результате могут произойти любые странные вещи.
Плохая аналогия: представьте себе сложный склад доставки, где склад - это память, а люди выбирают вещи - это процессор. Одна часть складской программы размещает на полке различные предметы. Другая программа отправляет вещи со склада и складывает их в коробки. Когда они сняты, они не проверяются, они просто идут в мусорное ведро. Весь склад функционирует благодаря тому, что все работает синхронно, при этом нужные элементы всегда находятся в нужном месте в нужное время, в противном случае все происходит сбой, как в реальной программе.
источник
Это не так. Как только C скомпилирован в машинный код, машина просто видит кучу битов. То, как эти биты интерпретируются, зависит от того, какие операции над ними выполняются, в отличие от некоторых дополнительных метаданных.
Типы, которые вы вводите в свой исходный код, предназначены только для компилятора. Он принимает, какой тип, как вы говорите, должны быть данные, и в меру своих возможностей пытается убедиться, что эти данные используются только способами, которые имеют смысл. После того, как компилятор выполнил как можно больше работы по проверке логики вашего исходного кода, он преобразует его в машинный код и отбрасывает данные типа, потому что машинный код не может это представить (по крайней мере, на большинстве машин). ,
источник
int a = 65
иchar b = 'A'
после того, как код скомпилирован.Большинство процессоров предоставляют разные инструкции для работы с данными разных типов, поэтому информация о типах обычно «заполняется» сгенерированным машинным кодом. Нет необходимости хранить дополнительные метаданные типа.
Некоторые конкретные примеры могут помочь. Приведенный ниже машинный код был сгенерирован с использованием gcc 4.1.2 в системе x86_64 под управлением SuSE Linux Enterprise Server (SLES) 10.
Предположим, следующий исходный код:
Вот суть сгенерированного ассемблерного кода, соответствующего вышеуказанному источнику (используя
gcc -S
), с добавленными мной комментариямиДалее следуют некоторые дополнительные вещи
ret
, но они не имеют отношения к обсуждению.%eax
32-битный регистр данных общего назначения%rsp
является 64-битным регистром, зарезервированным для сохранения указателя стека , который содержит адрес последней вещи, помещенной в стек.%rbp
является 64-битным регистром, зарезервированным для сохранения указателя кадра , который содержит адрес текущего кадра стека . Кадр стека создается в стеке при вводе функции и резервирует место для аргументов функции и локальных переменных. Доступ к аргументам и переменным осуществляется с помощью смещений из указателя кадра. В этом случае память для переменнойx
составляет 12 байтов «ниже» адреса, сохраненного в%rbp
.В приведенном выше коде мы копируем целочисленное значение
x
(1, сохраненное в-12(%rbp)
) в регистр,%eax
используяmovl
инструкцию, которая используется для копирования 32-битных слов из одного места в другое. Затем мы вызываемaddl
, который добавляет целочисленное значениеy
(хранится в-8(%rbp)
) к значению, уже в%eax
. Затем мы сохраняем результат в-4(%rbp)
, который естьz
.Теперь давайте изменим это, чтобы мы имели дело со
double
значениями вместоint
значений:Запуск
gcc -S
снова дает нам:Несколько отличий. Вместо
movl
иaddl
мы используемmovsd
иaddsd
(назначаем и добавляем поплавки двойной точности). Вместо того, чтобы хранить промежуточные значения в%eax
, мы используем%xmm0
.Это то, что я имею в виду, когда говорю, что тип «запекается» в машинном коде. Компилятор просто генерирует правильный машинный код для обработки этого конкретного типа.
источник
Исторически C считал память состоящей из нескольких групп пронумерованных слотов типа
unsigned char
(также называется «байт», хотя он не обязательно должен быть 8 бит). Любой код, который использовал что-либо, хранящееся в памяти, должен знать, в каком слоте или слотах хранится информация, и знать, что делать с этой информацией [например, интерпретировать четыре байта, начиная с адреса 123: 456, как 32-битный. значение с плавающей запятой "или" сохраняет младшие 16 битов последней вычисленной величины в два байта, начиная с адреса 345: 678]. Сама память не будет ни знать, ни заботиться о том, что "значения" хранятся в слотах памяти. Код пытался записать память с использованием одного типа и считывать его как другой, битовые комбинации, сохраненные при записи, будут интерпретироваться в соответствии с правилами второго типа, что может привести к любым последствиям.Например, если код должен был быть сохранен
0x12345678
в 32-разрядном форматеunsigned int
, а затем попытаться прочитать два последовательных 16-разрядныхunsigned int
значения из его адреса и указанного выше, то в зависимости от того,unsigned int
где и где была сохранена половина кода, код может прочитать значения 0x1234 и 0x5678 или 0x5678 и 0x1234.Стандарт C99, однако, больше не требует, чтобы память вела себя как набор пронумерованных слотов, которые ничего не знают о том, что представляют их битовые комбинации . Компилятору разрешается вести себя так, как будто слоты памяти осведомлены о типах данных, хранящихся в них, и разрешать только те данные, которые записаны с использованием любого типа, кроме как
unsigned char
для чтения, используя либо тип,unsigned char
либо тот же тип, который был записан с участием; компиляторам также разрешается вести себя так, как будто слоты памяти обладают силой и склонностью произвольно искажать поведение любой программы, которая пытается получить доступ к памяти способом, противоречащим этим правилам.Дано:
некоторые реализации могли бы печатать 0x1234, а другие могли бы печатать 0x5678, но в соответствии со стандартом C99 для реализации было бы законно печатать "FRINK RULES!" или делать что-либо еще, исходя из теории, что было бы законно, чтобы в ячейках памяти содержалось
a
оборудование, которое записывает, какой тип использовался для их записи, и чтобы такое оборудование реагировало на недопустимую попытку чтения любым способом, в том числе путем "ФРИНК ПРАВИЛА!" быть выведенным.Обратите внимание, что не имеет значения, существует ли такое оборудование на самом деле - тот факт, что такое оборудование может существовать на законных основаниях, делает законным для компиляторов генерировать код, который ведет себя так, как будто он работает в такой системе. Если компилятор может определить, что определенная ячейка памяти будет записана как один тип, а читается как другой, он может притвориться, что работает в системе, аппаратное обеспечение которой может сделать такое определение, и может ответить с любой степенью каприза, который автор компилятора сочтет целесообразным ,
Цель этого правила состояла в том, чтобы позволить компиляторам, которые знали, что группа байтов, содержащих значение некоторого типа, содержало определенное значение в определенный момент времени, и что с тех пор не было записано значение того же типа, выводить, что эта группа байтов все еще будет содержать это значение. Например, процессор считал группу байтов в регистр, а затем захотел снова использовать ту же информацию, пока она еще была в регистре, компилятор мог использовать содержимое регистра без необходимости перечитывать значение из памяти. Полезная оптимизация. В течение первых десяти лет применения этого правила, как правило, это означает, что если переменная записана с типом, отличным от того, который используется для ее чтения, запись может повлиять или не повлиять на прочитанное значение. Такое поведение может в некоторых случаях иметь катастрофические последствия, но в других случаях может быть безвредным,
Однако в 2009 году авторы некоторых компиляторов, таких как CLANG, определили, что, поскольку стандарт позволяет компиляторам делать все, что им нравится, в случаях, когда память записывается с использованием одного типа и читается как другой, компиляторы должны сделать вывод, что программы никогда не получат ввод, который мог бы вызвать такую вещь, чтобы произойти. Поскольку в стандарте говорится, что компилятору разрешено делать все, что ему нравится, когда получен такой недопустимый ввод, код, который будет иметь эффект только в тех случаях, когда стандарт не предъявляет никаких требований, может (и, по мнению некоторых авторов компилятора, должен) быть опущен как неактуально Это изменяет поведение нарушений псевдонимов по сравнению с тем, что память, которая, учитывая запрос на чтение, может произвольно возвращать последнее значение, записанное с использованием того же типа, что и запрос на чтение, или любое более недавнее значение, записанное с использованием какого-либо другого типа,
источник
int x,y,z;
выражениеx*y > z
никогда не будет делать ничего, кроме возврата 1 или 0, или где нарушения псевдонимов будут иметь какой-либо эффект кроме как позволить компилятору произвольно вернуть либо старое, либо новое значение.unsigned char
значения, которые используются для создания типа "пришли". Если программа должна была разложить указатель наunsigned char[]
, быстро отобразить его шестнадцатеричное содержимое на экране, а затем стереть указательunsigned char[]
, а затем принять некоторые шестнадцатеричные числа с клавиатуры, скопировать их обратно в указатель и затем разыменовать этот указатель Поведение будет четко определено в случае, когда набранное число совпадает с отображаемым числом.В С этого нет. Другие языки (например, Lisp, Python) имеют динамические типы, но C статически типизирован. Это означает, что ваша программа должна знать, какой тип данных должен интерпретироваться как символ, целое число и т. Д.
Обычно компилятор позаботится об этом за вас, и если вы сделаете что-то не так, вы получите ошибку во время компиляции (или предупреждение).
источник
10001
. Это ваша работа или работа компилятора , в зависимости от случая, чтобы идти в ногу с такими вещами вручную при написании машинного или ассемблерного кода.Вы должны различать
compiletime
иruntime
с одной стороны , и ,code
иdata
с другой стороны.С точки зрения машины нет разницы между тем, что вы называете
code
илиinstructions
тем, что вы называетеdata
. Все сводится к цифрам. Но некоторые последовательности - то, что мы бы назвалиcode
- делают то, что мы считаем полезным, другие - простоcrash
машина.Работа, выполняемая ЦП, представляет собой простой 4-х шаговый цикл:
instruction
)Это называется инструктивным циклом .
a
иx
являются переменными, которые являются заполнителями для адресов, где программа могла бы найти «содержимое» переменных. Таким образом, каждый раз, когда используется переменнаяa
, фактически существует адрес используемого содержимогоa
.Казнь не знает ничего. Из того, что было сказано во введении, процессор только выбирает данные и интерпретирует эти данные как инструкции.
Функция printf предназначена для того, чтобы «знать», какой тип ввода вы вводите в нее, то есть ее результирующий код дает правильные инструкции, как обращаться с особым сегментом памяти. Конечно, есть возможность скопировать бессмысленный вывод: использование адреса, в котором не хранится ни одной строки вместе с «% s»
printf()
, приведет к тому, что бессмысленный вывод будет остановлен только случайным местом в памяти, где 0 (\0
).То же самое касается точки входа в программу. Под C64 можно было помещать ваши программы в (почти) каждый известный адрес. Ассемблер-программы были запущены с инструкции, которая называлась
sys
адресом: этоsys 49152
было обычное место для написания кода вашего ассемблера. Но ничто не помешало вам загрузить, например, графические данные49152
, что привело к падению машины после «запуска» с этой точки. В этом случае цикл инструкций начинался со считывания «графических данных» и попытки интерпретировать их как «код» (что, конечно, не имело смысла); эффекты были иногда поразительными;)Как сказано: «контекст» - то есть предыдущие и последующие инструкции - помогают обрабатывать данные так, как мы хотим. С точки зрения машины, нет разницы ни в каком месте памяти.
int
иchar
это только словарный запас, который имеет смысл вcompiletime
; во времяruntime
(на уровне сборки) нетchar
илиint
.Компьютер ничего не знает . Программист делает. Скомпилированный код генерирует контекст , который необходим для получения значимых результатов для людей.
Да и нет . Информация, будь то
int
илиchar
потерян. Но с другой стороны, контекст (инструкции, которые говорят, как обращаться с областями памяти, где хранятся данные) сохраняется; так имплицитно да, «информация» имплицитно доступна.источник
Давайте оставим это обсуждение только на языке Си .
Программа, на которую вы ссылаетесь, написана на языке высокого уровня, таком как C. Компьютер понимает только машинный язык. Языки более высокого уровня дают программисту возможность выражать логику более удобным для человека способом, который затем переводится в машинный код, который микропроцессор может декодировать и выполнять. Теперь давайте обсудим код, который вы упомянули:
Давайте попробуем проанализировать каждую часть:
Таким образом, идентификаторы типа данных int / char используются только компилятором, а не микропроцессором во время выполнения программы. Следовательно, они не хранятся в памяти.
источник
Мой ответ здесь несколько упрощен и будет относиться только к C.
Нет, информация о типе не сохраняется в программе.
int
илиchar
не являются индикаторами типа процессора; только компилятору.Exe, созданный компилятором, будет иметь инструкции для манипулирования
int
s, если переменная была объявлена какint
. Аналогично, если переменная была объявлена как achar
, exe будет содержать инструкции для манипулирования achar
.В С:
Эта программа напечатает свое сообщение, так как
char
и в оперативной памятиint
имеют одинаковые значения .Теперь, если вам интересно, как
printf
удается выводить65
дляint
иA
дляchar
, то это потому, что вы должны указать в «строке формата», какprintf
следует обрабатывать значение .(Например,
%c
означает, что значение рассматривается как achar
, и%d
означает, что значение рассматривается как целое число; в любом случае, то же значение.)источник
printf
. @OP:int a = 65; printf("%c", a)
выведет'A'
. Почему? Потому что процессор не волнует. Для него все, что он видит, это биты. Ваша программа сказала процессору хранить 65 (по совпадению значение'A'
в ASCII)a
и затем выводить символ, что с удовольствием делает. Почему? Потому что это не волнует.На самом низком уровне в реальном физическом процессоре вообще нет типов (игнорируя единицы с плавающей запятой). Просто шаблоны битов. Компьютер работает, манипулируя структурами битов, очень, очень быстро.
Это все, что делает процессор, все, что он может сделать. Нет такой вещи как int или char.
Будет выполнять как:
Инструкция iadd запускает аппаратное обеспечение, которое ведет себя так, как будто регистры 1 и 2 являются целыми числами. Если они на самом деле не представляют собой целые числа, позже все может пойти не так. Лучший результат обычно терпит крах.
Компилятор должен выбрать правильную инструкцию, основанную на типах, указанных в исходном коде, но в реальном машинном коде, выполняемом процессором, нигде нет типов.
редактирование: обратите внимание, что фактический машинный код фактически не упоминает 4, 5 или целое число где-либо. это просто два набора битов и инструкция, которая принимает два набора битов, предполагает, что они являются целочисленными, и складывает их вместе.
источник
Короткий ответ, тип закодирован в инструкциях процессора, которые генерирует компилятор.
Хотя информация о типе или размере информации не сохраняется напрямую, компилятор отслеживает эту информацию при доступе, изменении и хранении значений в этих переменных.
Это не так, но когда компилятор создает машинный код, он знает. An
int
и Achar
могут быть разных размеров. В архитектуре, где char - это размер байта, а int - 4 байта, переменнаяx
находится не по адресу 10001, а также по 10002, 10003 и 10004. Когда коду необходимо загрузить значениеx
в регистр ЦП, он использует инструкцию для загрузки 4 байтов. При загрузке символа используется инструкция для загрузки 1 байта.Как выбрать, какая из двух инструкций? Компилятор решает во время компиляции, это не делается во время выполнения после проверки значений в памяти.
Обратите внимание, что регистры могут быть разных размеров. На процессорах Intel x86 EAX имеет ширину 32 бита, половину составляет AX, то есть 16, а AX делится на AH и AL, оба на 8 бит.
Поэтому, если вы хотите загрузить целое число (на процессорах x86), вы используете инструкцию MOV для целых чисел, чтобы загрузить символ, вы используете инструкцию MOV для символов. Они оба называются MOV, но у них разные коды операций. Эффективно быть две разные инструкции. Тип переменной кодируется в инструкции по использованию.
То же самое происходит с другими операциями. Существует много инструкций для выполнения сложения, в зависимости от размера операндов, и даже если они подписаны или не подписаны. См. Https://en.wikipedia.org/wiki/ADD_(x86_instruction), где перечислены различные возможные дополнения.
Во-первых, char будет 10011001, но int будет 00000000 00000000 00000000 10011001, потому что они имеют разные размеры (на компьютере с такими же размерами, как указано выше). Но давайте рассмотрим случай для
signed char
противunsigned char
.То, что хранится в ячейке памяти, можно интерпретировать как угодно. Часть обязанностей компилятора C состоит в том, чтобы гарантировать, что то, что хранится и читается из переменной, выполняется согласованным образом. Таким образом, дело не в том, что программа знает, что хранится в ячейке памяти, а в том, что она заранее согласна с тем, что она всегда будет читать и записывать одни и те же вещи. (не считая таких вещей, как типы кастинга).
источник
В языках с проверкой типов, таких как C #, проверка типов выполняется компилятором. Код Бенджи написал:
Просто отказался бы откомпилировать. Аналогично, если вы попытались умножить строку и целое число (я собирался сказать «добавить», но оператор «+» перегружен конкатенацией строк, и это может просто сработать).
Компилятор просто откажется генерировать машинный код из этого C #, независимо от того, насколько ваша строка его поцеловала.
источник
Другие ответы верны в том, что практически каждое потребительское устройство, с которым вы столкнетесь, не хранит информацию о типе. Однако в прошлом было несколько вариантов аппаратного обеспечения (и в настоящее время в контексте исследований), в которых использовалась теговая архитектура - они хранят как данные, так и тип (и, возможно, другую информацию). Это наиболее заметно включает машины Lisp .
Я смутно припоминаю, что слышал об аппаратной архитектуре, предназначенной для объектно-ориентированного программирования, в которой было что-то похожее, но сейчас я не могу ее найти.
источник