Как C знает, какого типа ожидать?

Если все значения представляют собой не более одного или более байтов, а байт не может содержать метаданные, как система отслеживает, какое количество байтов представляет? Взгляд на Two Complement и Single Point в Википедии показывает, как эти числа могут быть представлены в базе два, но мне все еще остается интересно, как компилятор или процессор (не уверенный, что я действительно имею в виду здесь) определяет, что этот байт должен - целое число со знаком.

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

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

В основном я имею дело с C в системе Unix, но это может быть более общий вопрос.

Ответ 1

как система отслеживает, какое количество байтов представляет?

"Система" этого не делает. Во время перевода компилятор знает типы объектов, с которыми он имеет дело, и генерирует соответствующие машинные инструкции для работы с этими значениями.

Ответ 2

О, хороший вопрос. Начните с процессора - предположим, что чип Intel x86.

Оказывается, ЦП не знает, является ли байт "подписанным" или "беззнаковым". Поэтому, когда вы добавляете два числа - или выполняете какую-либо операцию - устанавливается флаг status register.

Взгляните на "флаг знака". Когда вы добавляете два числа, CPU делает именно это - добавляет числа и сохраняет результат в регистре. Но CPU говорит: "Если вместо этого мы интерпретировали эти числа как два знака, дополняющие целые числа, является ли результат отрицательным?" Если это так, то этот флаг знака установлен в 1.

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

Итак, когда вы используете signed int versus unsigned int в C, вы в основном говорите компилятору, как (или) использовать этот флаг знака.

Ответ 3

Важно помнить, что C и С++ являются языками высокого уровня. Задача компилятора состоит в том, чтобы принять текстовое представление кода и построить его в определенных для платформы инструкциях, которые ожидает ожидаемая платформа. Для большинства людей, использующих ПК, это сборка x86.

Вот почему C и С++ настолько свободны, что определяют основные типы данных. Например, большинство людей говорят, что в байте 8 бит. Это не определено стандартом, и нет ничего против какой-либо машины, имеющей 7 бит на каждый байт в качестве собственной интерпретации данных. Стандарт только признает, что байт является наименьшей адресуемой единицей данных.

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

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

Ответ 4

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

Вот почему вам нужно использовать строки формата в printf, например. printf не имеет возможности узнать, какой тип он получит в списке параметров, поскольку эта информация будет потеряна. В таких языках, как go или java, у вас есть среда выполнения с возможностями отражения, которая позволяет получить тип.

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

Один пример сборки, поддерживающий тип, - это байт-код Java Java, который имеет суффиксы типа для операндов для примитивов.

Ответ 5

Используя C помимо компилятора, который отлично знает о типе данных значений, нет system, который знает о типе заданного значения. p >

Обратите внимание, что C сам по себе не приносит с собой никакой информационной системы типа времени выполнения.

Взгляните на следующий пример:

int i_var;
double d_var;

int main () {

  i_var = -23;
  d_var = 0.1;

  return 0;
}

В коде есть два разных типа значений, которые должны быть сохранены как целое, а одно - для двойного значения.

Компилятор, который хорошо анализирует код, знает о конкретных типах этих двух. Здесь дамп короткого фрагмента информации о типе gcc, хранящийся во время генерации кода, генерируемого передачей -fdump-tree-all в gcc:

@1      type_decl        name: @2       type: @3       srcp: <built-in>:0      
                         chan: @4      
@2      identifier_node  strg: int      lngt: 3       
@3      integer_type     name: @1       size: @5       algn: 32      
                         prec: 32       sign: signed   min : @6      
                         max : @7      
...
@5      integer_cst      type: @11      low : 32      
@6      integer_cst      type: @3       high: -1       low : -2147483648 
@7      integer_cst      type: @3       low : 2147483647 
...

@3805   var_decl         name: @3810    type: @3       srcp: main.c:3      
                         chan: @3811    size: @5       algn: 32      
                         used: 1       
...
@3810   identifier_node  strg: i_var    lngt: 5    

Охватывая @links, вы должны четко видеть, что на самом деле хранится много информации о размерах памяти, ограничениях выравнивания и разрешенных минимальных и максимальных значениях для типа "int", хранящихся в узлах @1- 3 и @5-7. (Я оставил @4 node, поскольку указанная запись "chan" используется только для cha я n для определения любого типа в сгенерированном дереве)

Обоснование переменной, объявленной в строке main.c 3, известно, что она удерживает значение типа int, как видно по типу ссылки на node @3.

Вы наверняка сможете самостоятельно выследить двойные записи и те, что для d_var, в собственном эксперименте, если вы не доверяете мне, что они тоже будут там.

Взглянув на сгенерированный код ассемблера (используя gcc pass the -S switch), мы можем посмотреть, как компилятор использовал эту информацию при генерации кода:

    .file   "main.c"
    .comm   i_var,4,4
    .comm   d_var,8,8
    .text
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    movl    $-23, i_var
    fldl    .LC0
    fstpl   d_var
    movl    $0, %eax
    popl    %ebp
    ret
    .size   main, .-main
    .section    .rodata
    .align 8
.LC0:
    .long   -1717986918
    .long   1069128089
    .ident  "GCC: (Debian 4.4.5-8) 4.4.5"
    .section    .note.GNU-stack,"",@progbits

Взглянув на инструкции назначения, вы увидите, что компилятор определил правильные инструкции "mov", чтобы присвоить нашему значению int и "fstp", чтобы присвоить наше "двойное" значение.

Тем не менее, помимо инструкций, выбранных на уровне машины, нет указаний на тип этих значений. Взглянув на значение, хранящееся на .LC0, тип "double" значения 0.1 был даже разбит в двух последовательных местах хранения, каждый из которых долго встречался с известными "типами" ассемблера.

Фактически, разбить значение таким образом было всего лишь одним выбором других возможностей, используя 8 последовательных значений типа ".". Было бы хорошо.