Как типы данных C «напрямую поддерживаются большинством компьютеров»?

114

Я читал книгу K&R «Язык программирования C» и наткнулся на это утверждение [Введение, стр. 3]:

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

Что означает заявление, выделенное жирным шрифтом? Есть ли пример типа данных или управляющей структуры, которые напрямую не поддерживаются компьютером?

GWG
источник
1
В наши дни язык C поддерживает сложную арифметику, но изначально этого не произошло, потому что компьютеры не поддерживают напрямую сложные числа как типы данных.
Джонатан Леффлер
12
На самом деле, исторически все было наоборот: C был разработан на основе аппаратных операций и типов, доступных в то время.
Василий Старынкевич
2
Большинство компьютеров не имеют прямой аппаратной поддержки десятичных чисел с плавающей запятой
PlasmaHH
3
@MSalters: Я пытался намекнуть в каком-то направлении на вопрос «Есть ли пример типа данных или управляющей структуры, которые не поддерживаются напрямую компьютером?» который я не интерпретировал как ограниченный K&R
PlasmaHH
11
Как это не дублировать более чем через 6 лет после запуска Stack Overflow?
Питер Мортенсен

Ответы:

143

Да, есть типы данных, которые напрямую не поддерживаются.

Во многих встроенных системах нет аппаратного модуля с плавающей запятой. Итак, когда вы пишете такой код:

float x = 1.0f, y = 2.0f;
return x + y;

Это переводится примерно так:

unsigned x = 0x3f800000, y = 0x40000000;
return _float_add(x, y);

Затем компилятор или стандартная библиотека должны предоставить реализацию _float_add(), которая занимает память вашей встроенной системы. Если вы считаете байты в действительно крошечной системе, это может сложиться.

Другой распространенный пример - 64-битные целые числа ( long longв стандарте C с 1999 года), которые напрямую не поддерживаются 32-битными системами. Старые системы SPARC не поддерживали целочисленное умножение, поэтому умножение должно было обеспечиваться средой выполнения. Есть и другие примеры.

Другие языки

Для сравнения, другие языки имеют более сложные примитивы.

Например, символ Lisp требует большой поддержки во время выполнения, как и таблицы в Lua, строки в Python, массивы в Fortran и т. Д. Эквивалентные типы в C обычно либо вообще не являются частью стандартной библиотеки (без стандартных символов или таблиц), либо они намного проще и не требуют большой поддержки во время выполнения (массивы в C в основном просто указатели, строки с завершающим нулем - почти так же просто).

Структуры управления

Примечательной структурой управления, отсутствующей в C, является обработка исключений. Нелокальный выход ограничен setjmp()и longjmp(), которые просто сохраняют и восстанавливают определенные части состояния процессора. Для сравнения, среда выполнения C ++ должна обходить стек и вызывать деструкторы и обработчики исключений.

Дитрих Эпп
источник
2
в основном просто указатели ... скорее, в основном необработанные фрагменты памяти. Даже если это придирки, а ответ в любом случае хороший.
Дедупликатор
2
Вы можете утверждать, что строки с завершающим нулем имеют «аппаратную поддержку», поскольку терминатор строки подходит для операции «переход при нулевом значении» большинства процессоров и, следовательно, немного быстрее, чем другие возможные реализации строк.
Петерис
1
Отправил свой собственный ответ, чтобы подробнее рассказать о том, как C предназначен для простого сопоставления с asm.
Питер Кордес
1
Пожалуйста, не используйте словосочетание «массивы - это просто указатели», это может серьезно ввести в заблуждение новичка, такого как OP. Что-то вроде «массивы реализуются напрямую с использованием указателей на аппаратном уровне» было бы лучше IMO.
Парамагнитный круассан,
1
@TheParamintageCroissant: Думаю, в этом контексте это уместно ... ясность достигается за счет точности.
Дитрих Эпп
37

На самом деле, я готов поспорить, что содержание этого введения не сильно изменилось с 1978 года, когда Керниган и Ричи впервые написали их в первом издании книги, и они относятся к истории и эволюции Си в то время больше, чем современные. реализации.

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

Авторы языка C - и языков B и BCPL, которые ему предшествовали - стремились определить конструкции на языке, которые были бы настолько эффективно скомпилированы в Assembly, насколько это возможно ... фактически, они были вынуждены сделать это из-за ограничений целевого объекта. аппаратное обеспечение. Как указывалось в других ответах, это связано с ветвями (GOTO и другое управление потоком в C), перемещениями (присваиванием), логическими операциями (& | ^), базовой арифметикой (сложение, вычитание, увеличение, уменьшение) и адресацией памяти (указатели ). Хорошим примером являются операторы пре- / пост-инкремента и декремента в C, которые предположительно были добавлены в язык B Кеном Томпсоном специально потому, что они были способны преобразовывать непосредственно в один код операции после компиляции.

Это то, что имели в виду авторы, когда они сказали «поддерживается напрямую большинством компьютеров». Они не означает , что другие языки содержатся типы и структуры, которые не поддерживаются непосредственно - они имели в виду , что в соответствии с проектом C конструкции переведены наиболее непосредственно (иногда буквально непосредственно) в Ассамблее.

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

Для интересного обзора истории языка см . Развитие языка C - Деннис Ричи.

Джон Кастлман
источник
14

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

Более длинный ответ требует немного знания ассемблера. В языке C такой оператор:

int myInt = 10;

переводится в сборку примерно так:

myInt dw 1
mov myInt,10

Сравните это с чем-то вроде C ++:

MyClass myClass;
myClass.set_myInt(10);

В результате код языка ассемблера (в зависимости от размера MyClass ()) может добавить до сотен строк на языке ассемблера.

На самом деле, не создавая программ на ассемблере, чистый C, вероятно, будет самым «тонким» и «сложным» кодом, на котором вы можете написать программу.

РЕДАКТИРОВАТЬ

Учитывая комментарии к моему ответу, я решил провести тест, просто ради собственного здравомыслия. Я создал программу под названием «test.c», которая выглядела так:

#include <stdio.h>

void main()
{
    int myInt=10;

    printf("%d\n", myInt);
}

Я скомпилировал это до сборки с помощью gcc. Я использовал следующую командную строку для его компиляции:

gcc -S -O2 test.c

Вот получившийся язык ассемблера:

    .file   "test.c"
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "%d\n"
    .section    .text.unlikely,"ax",@progbits
.LCOLDB1:
    .section    .text.startup,"ax",@progbits
.LHOTB1:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB24:
    .cfi_startproc
    movl    $10, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    jmp __printf_chk
    .cfi_endproc
.LFE24:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE1:
    .section    .text.startup
.LHOTE1:
    .ident  "GCC: (Ubuntu 4.9.1-16ubuntu6) 4.9.1"
    .section    .note.GNU-stack,"",@progbits

Затем я создаю файл с именем «test.cpp», который определяет класс и выводит то же, что и «test.c»:

#include <iostream>
using namespace std;

class MyClass {
    int myVar;
public:
    void set_myVar(int);
    int get_myVar(void);
};

void MyClass::set_myVar(int val)
{
    myVar = val;
}

int MyClass::get_myVar(void)
{
    return myVar;
}

int main()
{
    MyClass myClass;
    myClass.set_myVar(10);

    cout << myClass.get_myVar() << endl;

    return 0;
}

Я скомпилировал его таким же образом, используя эту команду:

g++ -O2 -S test.cpp

Вот получившийся файл сборки:

    .file   "test.cpp"
    .section    .text.unlikely,"ax",@progbits
    .align 2
.LCOLDB0:
    .text
.LHOTB0:
    .align 2
    .p2align 4,,15
    .globl  _ZN7MyClass9set_myVarEi
    .type   _ZN7MyClass9set_myVarEi, @function
_ZN7MyClass9set_myVarEi:
.LFB1047:
    .cfi_startproc
    movl    %esi, (%rdi)
    ret
    .cfi_endproc
.LFE1047:
    .size   _ZN7MyClass9set_myVarEi, .-_ZN7MyClass9set_myVarEi
    .section    .text.unlikely
.LCOLDE0:
    .text
.LHOTE0:
    .section    .text.unlikely
    .align 2
.LCOLDB1:
    .text
.LHOTB1:
    .align 2
    .p2align 4,,15
    .globl  _ZN7MyClass9get_myVarEv
    .type   _ZN7MyClass9get_myVarEv, @function
_ZN7MyClass9get_myVarEv:
.LFB1048:
    .cfi_startproc
    movl    (%rdi), %eax
    ret
    .cfi_endproc
.LFE1048:
    .size   _ZN7MyClass9get_myVarEv, .-_ZN7MyClass9get_myVarEv
    .section    .text.unlikely
.LCOLDE1:
    .text
.LHOTE1:
    .section    .text.unlikely
.LCOLDB2:
    .section    .text.startup,"ax",@progbits
.LHOTB2:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB1049:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $10, %esi
    movl    $_ZSt4cout, %edi
    call    _ZNSolsEi
    movq    %rax, %rdi
    call    _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
    xorl    %eax, %eax
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc
.LFE1049:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE2:
    .section    .text.startup
.LHOTE2:
    .section    .text.unlikely
.LCOLDB3:
    .section    .text.startup
.LHOTB3:
    .p2align 4,,15
    .type   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi, @function
_GLOBAL__sub_I__ZN7MyClass9set_myVarEi:
.LFB1056:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $_ZStL8__ioinit, %edi
    call    _ZNSt8ios_base4InitC1Ev
    movl    $__dso_handle, %edx
    movl    $_ZStL8__ioinit, %esi
    movl    $_ZNSt8ios_base4InitD1Ev, %edi
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    jmp __cxa_atexit
    .cfi_endproc
.LFE1056:
    .size   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi, .-_GLOBAL__sub_I__ZN7MyClass9set_myVarEi
    .section    .text.unlikely
.LCOLDE3:
    .section    .text.startup
.LHOTE3:
    .section    .init_array,"aw"
    .align 8
    .quad   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi
    .local  _ZStL8__ioinit
    .comm   _ZStL8__ioinit,1,1
    .hidden __dso_handle
    .ident  "GCC: (Ubuntu 4.9.1-16ubuntu6) 4.9.1"
    .section    .note.GNU-stack,"",@progbits

Как вы можете ясно видеть, получившийся файл сборки намного больше в файле C ++, чем в файле C. Даже если вы уберете все остальное и просто сравните "main" C с "main" C ++, останется много лишних вещей.

Icemanind
источник
14
Этот «код C ++» просто не C ++. А реальный код, такой как MyClass myClass { 10 }C ++, скорее всего, будет компилироваться в точно такую ​​же сборку. Современные компиляторы C ++ устранили штраф за абстракцию. В результате они часто могут превзойти компиляторы C. Например, штраф за абстракцию в C qsortреален, но в C ++ std::sortнет штрафа за абстракцию даже после базовой оптимизации.
MSalters
1
Используя IDA Pro, вы можете легко увидеть, что большинство конструкций C ++ компилируются до того же уровня, что и ручное выполнение в C, конструкторы и dtors встроены для тривиальных объектов, а затем применяется будущая оптимизация
Paulm
7

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

Вот цитата:

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

( нашел здесь . Мне показалось, что я вспомнил другой вариант, например, «скорость ассемблера с удобством и выразительностью ассемблера».)

long int обычно имеет ту же ширину, что и машинные регистры.

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

Если вы хотите работать со 128-битными int на x86-64 или в общем случае BigInteger произвольного размера, вам понадобится библиотека функций для него. Все процессоры теперь используют дополнение до двух в качестве двоичного представления отрицательных целых чисел, но даже это было не так, когда был разработан C. (Вот почему некоторые вещи, которые давали бы разные результаты на машинах, не дополняющих 2s, технически не определены в стандартах C.)

Указатели C на данные или на функции работают так же, как адреса сборки.

Если вам нужны ссылки с подсчетом ссылок, вы должны сделать это сами. Если вам нужны виртуальные функции-члены C ++, которые вызывают другую функцию в зависимости от того, на какой объект указывает ваш указатель, компилятор C ++ должен сгенерировать гораздо больше, чем просто callинструкцию с фиксированным адресом.

Строки - это просто массивы

Вне библиотечных функций единственные строковые операции - это чтение / запись символа. Без конкатенации, без подстроки, без поиска. (Строки хранятся как '\0'массивы 8-битных целых чисел с завершающим нулем ( ), а не указатель + длина, поэтому для получения подстроки вам нужно будет записать нуль в исходную строку.)

У ЦП иногда есть инструкции, предназначенные для использования функцией поиска строки, но все же обычно обрабатывают один байт на каждую выполняемую инструкцию в цикле. (или с префиксом rep x86. Возможно, если бы C был разработан для x86, поиск или сравнение строк было бы собственной операцией, а не вызовом библиотечной функции.)

Во многих других ответах приводятся примеры вещей, которые изначально не поддерживаются, например обработка исключений, хеш-таблицы, списки. Философия дизайна K&R является причиной того, что у C нет ни одного из них изначально.

Питер Кордес
источник
«K&R означает, что большинство выражений C (техническое значение) соответствуют одной или нескольким инструкциям по сборке, а не вызову функции в библиотеке поддержки». Это очень интуитивное объяснение. Спасибо.
gwg
1
Я только что наткнулся на термин «язык фон Неймана» ( en.wikipedia.org/wiki/Von_Neumann_programming_languages ). Это ТОЧНО, что такое C.
Питер Кордес
1
Именно поэтому я использую C. Но что меня удивило, когда я изучаю C, так это то, что попытки быть эффективными для широкого диапазона оборудования иногда оказываются неэффективными и неэффективными на большинстве современных устройств. Я имею в виду, например, отсутствие полезного и надежного способа обнаружения целочисленного переполнения в c и сложение нескольких слов с использованием флага переноса .
Z boson
6

Ассемблерный язык процесса обычно имеет дело с переходами (переходом), операторами, операторами перемещения, двоичными артритами (XOR, NAND, AND OR и т.д.), полями памяти (или адресами). Делит память на два типа: инструкции и данные. Это все, что есть на ассемблере (я уверен, что программисты на ассемблере будут утверждать, что это еще не все, но в целом все сводится к этому). C очень похож на эту простоту.

C состоит в том, чтобы соединить алгебру с арифметикой.

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

terary
источник
5

Остерегайтесь вводящих в заблуждение сравнений

  1. В заявлении используется понятие «библиотека времени выполнения» , которая с тех пор в основном вышла из моды, по крайней мере, для основных языков высокого уровня. (Это по-прежнему актуально для самых маленьких встроенных систем.) Время выполнения - это минимальная поддержка, которую программа на этом языке требует для выполнения, когда вы используете только конструкции, встроенные в язык (в отличие от явного вызова функции, предоставляемой библиотекой) ,
  2. Напротив, современные языки, как правило, не делают различий между средой выполнения и стандартной библиотекой , последняя часто бывает довольно обширной.
  3. Во время выхода книги K&R у C даже не было стандартной библиотеки . Скорее, доступные библиотеки C сильно различались между разными версиями Unix.
  4. Для понимания утверждения вы не должны сравнивать языки со стандартной библиотекой (например, Lua и Python, упомянутые в других ответах), а с языками с более встроенными конструкциями (такими как старый LISP и старый FORTRAN, упомянутые в других ответы). Другими примерами могут быть BASIC (интерактивный, как LISP) или PASCAL (скомпилированный, как FORTRAN), оба из которых имеют (среди прочего) функции ввода / вывода, встроенные прямо в сам язык.
  5. Напротив, нет стандартного способа получить результаты вычислений из программы C, которая использует только время выполнения, а не какую-либо библиотеку.
Лутц Прешельт
источник
С другой стороны, большинство современных языков работают в выделенных средах выполнения, которые предоставляют такие возможности, как сборка мусора.
Nate CK,
5

Есть ли пример типа данных или управляющей структуры, которые напрямую не поддерживаются компьютером?

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

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

Многие языки имеют специальный сокращенный синтаксис для таких типов и их операций - использование таких типов данных в C обычно требует ввода гораздо большего количества кода.

К таким типам данных и операциям относятся:

  • манипуляции с текстовой строкой произвольной длины - конкатенация, подстрока, присвоение новой строки переменной, инициализированной какой-либо другой строкой, и т. д. ('s = "Hello World!"; s = (s + s) [2: -2] 'в Python)
  • наборы
  • объекты с вложенными виртуальными деструкторами, как в C ++ и любом другом объектно-ориентированном языке программирования
  • Умножение и деление 2D-матриц; решение линейных систем ("C = B / A; x = A \ b" в MATLAB и многих языках программирования массивов)
  • регулярные выражения
  • массивы переменной длины - в частности, добавление элемента в конец массива, что (иногда) требует выделения дополнительной памяти.
  • чтение значения переменных, которые меняют тип во время выполнения - иногда это число с плавающей точкой, иногда - строка
  • ассоциативные массивы (часто называемые «картами» или «словарями»)
  • списки
  • отношения ("(+ 1/3 2/7)" дает "13/21" в Лиспе )
  • арифметика произвольной точности (часто называемая "бигнумами")
  • преобразование данных в представление для печати (метод ".tostring" в JavaScript)
  • насыщающие числа с фиксированной точкой (часто используются во встроенных программах на C)
  • оценка строки, введенной во время выполнения, как если бы это было выражение («eval ()» во многих языках программирования).

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

Вот некоторые популярные управляющие структуры, которые также требуют десятков инструкций на машинном языке или циклов:

  • укупорочные
  • продолжения
  • исключения
  • ленивая оценка

Независимо от того, написано ли оно на C или другом языке, когда программа манипулирует такими типами данных, ЦП должен в конечном итоге выполнить все инструкции, необходимые для управления этими типами данных. Эти инструкции часто содержатся в «библиотеке». Каждый язык программирования, даже C, имеет «библиотеку времени выполнения» для каждой платформы, которая по умолчанию включена в каждый исполняемый файл.

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

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

Дэвид Кэри
источник
Если ваша реализация Lisp оценивает (+ 1/3 2/7) как 3/21, я думаю, у вас должна быть особенно творческая реализация ...
RobertB
4

Какие встроенные типы данных C? Они такие вещи , как int, char, * int, float, массивы и т.д. ... Эти типы данных понимаются ЦП. ЦП знает, как работать с массивами, как разыменовать указатели и как выполнять арифметические операции с указателями, целыми числами и числами с плавающей запятой.

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

hhafez
источник
2
x86 умеет работать с некоторыми массивами, но не со всеми. Для больших или необычных размеров элементов потребуется выполнить целочисленную арифметику, чтобы преобразовать индекс массива в смещение указателя. А на других платформах это всегда нужно. А идея, что ЦП не понимает классов C ++, смехотворна. Это просто смещения указателя, как в C-структурах. Для этого вам не нужна среда выполнения.
MSalters
@MSalters да, но фактические методы классов стандартной библиотеки, такие как iostreams и т. Д., Являются библиотечными функциями, а не напрямую поддерживаются компилятором. Однако языки более высокого уровня, с которыми они, вероятно, сравнивали, был не C ++, а современные языки, такие как FORTRAN и PL / I.
Random832
1
Классы C ++ с виртуальными функциями-членами переводятся в нечто большее, чем просто смещение в структуру.
Питер Кордес
4

Это зависит от компьютера. На PDP-11, где был изобретен язык C, longон плохо поддерживался (можно было купить дополнительный дополнительный модуль, который поддерживал некоторые, но не все, 32-разрядные операции). То же самое в разной степени верно для любой 16-битной системы, включая оригинальный IBM PC. И то же самое для 64-битных операций на 32-битных машинах или в 32-битных программах, хотя язык C во время книги K&R вообще не имел никаких 64-битных операций. И, конечно же, в 80-х и 90-х годах было много систем [включая 386 и около 486 процессоров] и даже некоторые встроенные системы сегодня, которые не поддерживали напрямую арифметику с плавающей запятой ( floatили double).

В качестве более экзотического примера некоторые компьютерные архитектуры поддерживают только «ориентированные на слова» указатели (указывающие на двухбайтовое или четырехбайтовое целое число в памяти), а байтовые указатели ( char *или void *) должны были быть реализованы путем добавления дополнительного поля смещения. В этом вопросе подробно рассматриваются такие системы.

Функции "библиотеки времени выполнения", к которым он относится, - это не те, которые вы увидите в руководстве, а функции, подобные этим, в библиотеке времени выполнения современного компилятора , которые используются для реализации операций базового типа, которые не поддерживаются машиной. , Библиотеку времени выполнения, на которую ссылались сами K&R, можно найти на веб-сайте The Unix Heritage Society - вы можете увидеть такие функции, как ldiv(отличные от одноименной функции C, которая не существовала в то время), которая используется для реализации разделения 32-битные значения, которые PDP-11 не поддерживает даже с надстройкой, и csvcretтакже в csv.c), которые сохраняют и восстанавливают регистры в стеке для управления вызовами и возвратами из функций.

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

Random832
источник
3

Заявление просто означает, что структуры данных и управления в C ориентированы на машину.

Здесь следует учитывать два аспекта. Во-первых, у языка C есть определение (стандарт ISO), которое дает свободу в определении типов данных. Это означает, что реализации языка C адаптированы к машине . Типы данных компилятора C соответствуют тому, что доступно на машине, на которую нацелен компилятор, потому что у языка есть широта для этого. Если машина имеет необычный размер слова, например, 36 бит, то тип intили longможно настроить в соответствии с этим. Программы, которые предполагают, что intэто ровно 32 бита, сломаются.

Во-вторых, из-за таких проблем с переносимостью возникает второй эффект. В некотором смысле утверждение в K&R стало своего рода самоисполняющимся пророчеством , а может быть, и наоборот. То есть разработчики новых процессоров осознают острую потребность в поддержке компиляторов C, и они знают, что существует много кода C, который предполагает, что «каждый процессор выглядит как 80386». Архитектуры разрабатываются с учетом C: и не только с учетом C, но также с учетом распространенных заблуждений о переносимости C. Вы просто не можете представить машину с 9-битными байтами или чем-то еще для общего использования. Программы, предполагающие, что типcharимеет ширину ровно 8 бит. Только некоторые программы, написанные экспертами по переносимости, будут продолжать работать: вероятно, этого недостаточно для того, чтобы с разумными усилиями собрать полную систему с набором инструментов, ядром, пользовательским пространством и полезными приложениями. Другими словами, типы C выглядят как то, что доступно на оборудовании, потому что оборудование было сделано так, чтобы выглядеть как какое-то другое оборудование, для которого было написано много непереносимых программ на C.

Есть ли пример типа данных или управляющей структуры, которые напрямую не поддерживаются компьютером?

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

Структуры управления, не поддерживаемые напрямую в большинстве машинных языков: продолжение первого класса; сопрограмма / нить; генератор; Обработка исключений.

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

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

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

Кроме того, говоря о числах с плавающей точкой, существует стандартизация: числа с плавающей запятой IEEE 754. Почему ваш компилятор C имеет doubleформат, который согласуется с форматом с плавающей запятой, поддерживаемым процессором, не только потому, что они оба были согласованы, но потому, что существует независимый стандарт для этого представления.

Kaz
источник
2

Такие вещи, как

  • Списки Используются почти на всех функциональных языках.

  • Исключения .

  • Ассоциативные массивы (Карты) - включены, например, в PHP и Perl.

  • Сборка мусора .

  • Типы данных / управляющие структуры включены во многие языки, но напрямую не поддерживаются ЦП.

MTilsted
источник
2

Под непосредственной поддержкой следует понимать эффективное отображение на набор команд процессора.

  • Прямая поддержка целочисленных типов является правилом, за исключением длинных (могут потребоваться расширенные арифметические процедуры) и коротких размеров (может потребоваться маскирование).

  • Для прямой поддержки типов с плавающей запятой требуется наличие FPU.

  • Прямая поддержка битовых полей является исключительной.

  • Структуры и массивы требуют вычисления адреса, что в некоторой степени напрямую поддерживается.

  • Указатели всегда напрямую поддерживаются через косвенную адресацию.

  • goto / if / while / for / do напрямую поддерживаются безусловными / условными переходами.

  • Переключатель может напрямую поддерживаться, когда применяется таблица переходов.

  • Вызовы функций напрямую поддерживаются функциями стека.

Ив Дауст
источник