Я читал книгу K&R «Язык программирования C» и наткнулся на это утверждение [Введение, стр. 3]:
Поскольку типы данных и управляющие структуры, предоставляемые C, напрямую поддерживаются большинством компьютеров , библиотека времени выполнения, необходимая для реализации автономных программ, очень мала.
Что означает заявление, выделенное жирным шрифтом? Есть ли пример типа данных или управляющей структуры, которые напрямую не поддерживаются компьютером?
Ответы:
Да, есть типы данных, которые напрямую не поддерживаются.
Во многих встроенных системах нет аппаратного модуля с плавающей запятой. Итак, когда вы пишете такой код:
Это переводится примерно так:
Затем компилятор или стандартная библиотека должны предоставить реализацию
_float_add()
, которая занимает память вашей встроенной системы. Если вы считаете байты в действительно крошечной системе, это может сложиться.Другой распространенный пример - 64-битные целые числа (
long long
в стандарте C с 1999 года), которые напрямую не поддерживаются 32-битными системами. Старые системы SPARC не поддерживали целочисленное умножение, поэтому умножение должно было обеспечиваться средой выполнения. Есть и другие примеры.Другие языки
Для сравнения, другие языки имеют более сложные примитивы.
Например, символ Lisp требует большой поддержки во время выполнения, как и таблицы в Lua, строки в Python, массивы в Fortran и т. Д. Эквивалентные типы в C обычно либо вообще не являются частью стандартной библиотеки (без стандартных символов или таблиц), либо они намного проще и не требуют большой поддержки во время выполнения (массивы в C в основном просто указатели, строки с завершающим нулем - почти так же просто).
Структуры управления
Примечательной структурой управления, отсутствующей в C, является обработка исключений. Нелокальный выход ограничен
setjmp()
иlongjmp()
, которые просто сохраняют и восстанавливают определенные части состояния процессора. Для сравнения, среда выполнения C ++ должна обходить стек и вызывать деструкторы и обработчики исключений.источник
На самом деле, я готов поспорить, что содержание этого введения не сильно изменилось с 1978 года, когда Керниган и Ричи впервые написали их в первом издании книги, и они относятся к истории и эволюции Си в то время больше, чем современные. реализации.
По сути, компьютеры - это просто банки памяти и центральные процессоры, и каждый процессор работает с использованием машинного кода; частью конструкции каждого процессора является архитектура набора команд, называемая языком ассемблера , которая однозначно отображает набор удобочитаемых мнемоник в машинный код, состоящий только из чисел.
Авторы языка C - и языков B и BCPL, которые ему предшествовали - стремились определить конструкции на языке, которые были бы настолько эффективно скомпилированы в Assembly, насколько это возможно ... фактически, они были вынуждены сделать это из-за ограничений целевого объекта. аппаратное обеспечение. Как указывалось в других ответах, это связано с ветвями (GOTO и другое управление потоком в C), перемещениями (присваиванием), логическими операциями (& | ^), базовой арифметикой (сложение, вычитание, увеличение, уменьшение) и адресацией памяти (указатели ). Хорошим примером являются операторы пре- / пост-инкремента и декремента в C, которые предположительно были добавлены в язык B Кеном Томпсоном специально потому, что они были способны преобразовывать непосредственно в один код операции после компиляции.
Это то, что имели в виду авторы, когда они сказали «поддерживается напрямую большинством компьютеров». Они не означает , что другие языки содержатся типы и структуры, которые не поддерживаются непосредственно - они имели в виду , что в соответствии с проектом C конструкции переведены наиболее непосредственно (иногда буквально непосредственно) в Ассамблее.
Эта тесная связь с базовой сборкой, в то же время предоставляя все элементы, необходимые для структурированного программирования, - вот что привело к раннему принятию C и что делает его популярным сегодня языком в средах, где эффективность скомпилированного кода по-прежнему является ключевой.
Для интересного обзора истории языка см . Развитие языка C - Деннис Ричи.
источник
Короткий ответ: большинство языковых конструкций, поддерживаемых C, также поддерживаются микропроцессором целевого компьютера, поэтому скомпилированный код C очень хорошо и эффективно транслируется на язык ассемблера микропроцессора, что приводит к меньшему размеру кода и меньшим размерам.
Более длинный ответ требует немного знания ассемблера. В языке C такой оператор:
переводится в сборку примерно так:
Сравните это с чем-то вроде C ++:
В результате код языка ассемблера (в зависимости от размера MyClass ()) может добавить до сотен строк на языке ассемблера.
На самом деле, не создавая программ на ассемблере, чистый C, вероятно, будет самым «тонким» и «сложным» кодом, на котором вы можете написать программу.
РЕДАКТИРОВАТЬ
Учитывая комментарии к моему ответу, я решил провести тест, просто ради собственного здравомыслия. Я создал программу под названием «test.c», которая выглядела так:
Я скомпилировал это до сборки с помощью gcc. Я использовал следующую командную строку для его компиляции:
Вот получившийся язык ассемблера:
Затем я создаю файл с именем «test.cpp», который определяет класс и выводит то же, что и «test.c»:
Я скомпилировал его таким же образом, используя эту команду:
Вот получившийся файл сборки:
Как вы можете ясно видеть, получившийся файл сборки намного больше в файле C ++, чем в файле C. Даже если вы уберете все остальное и просто сравните "main" C с "main" C ++, останется много лишних вещей.
источник
MyClass myClass { 10 }
C ++, скорее всего, будет компилироваться в точно такую же сборку. Современные компиляторы C ++ устранили штраф за абстракцию. В результате они часто могут превзойти компиляторы C. Например, штраф за абстракцию в Cqsort
реален, но в C ++std::sort
нет штрафа за абстракцию даже после базовой оптимизации.K&R означает, что большинство выражений C (техническое значение) соответствуют одной или нескольким инструкциям по сборке, а не вызову функции в библиотеке поддержки. Обычные исключения - это целочисленное деление на архитектурах без аппаратной инструкции div или с плавающей запятой на машинах без FPU.
Вот цитата:
( нашел здесь . Мне показалось, что я вспомнил другой вариант, например, «скорость ассемблера с удобством и выразительностью ассемблера».)
long int обычно имеет ту же ширину, что и машинные регистры.
Некоторые языки более высокого уровня определяют точную ширину своих типов данных, и реализации на всех машинах должны работать одинаково. Но не С.
Если вы хотите работать со 128-битными int на x86-64 или в общем случае BigInteger произвольного размера, вам понадобится библиотека функций для него. Все процессоры теперь используют дополнение до двух в качестве двоичного представления отрицательных целых чисел, но даже это было не так, когда был разработан C. (Вот почему некоторые вещи, которые давали бы разные результаты на машинах, не дополняющих 2s, технически не определены в стандартах C.)
Указатели C на данные или на функции работают так же, как адреса сборки.
Если вам нужны ссылки с подсчетом ссылок, вы должны сделать это сами. Если вам нужны виртуальные функции-члены C ++, которые вызывают другую функцию в зависимости от того, на какой объект указывает ваш указатель, компилятор C ++ должен сгенерировать гораздо больше, чем просто
call
инструкцию с фиксированным адресом.Строки - это просто массивы
Вне библиотечных функций единственные строковые операции - это чтение / запись символа. Без конкатенации, без подстроки, без поиска. (Строки хранятся как
'\0'
массивы 8-битных целых чисел с завершающим нулем ( ), а не указатель + длина, поэтому для получения подстроки вам нужно будет записать нуль в исходную строку.)У ЦП иногда есть инструкции, предназначенные для использования функцией поиска строки, но все же обычно обрабатывают один байт на каждую выполняемую инструкцию в цикле. (или с префиксом rep x86. Возможно, если бы C был разработан для x86, поиск или сравнение строк было бы собственной операцией, а не вызовом библиотечной функции.)
Во многих других ответах приводятся примеры вещей, которые изначально не поддерживаются, например обработка исключений, хеш-таблицы, списки. Философия дизайна K&R является причиной того, что у C нет ни одного из них изначально.
источник
Ассемблерный язык процесса обычно имеет дело с переходами (переходом), операторами, операторами перемещения, двоичными артритами (XOR, NAND, AND OR и т.д.), полями памяти (или адресами). Делит память на два типа: инструкции и данные. Это все, что есть на ассемблере (я уверен, что программисты на ассемблере будут утверждать, что это еще не все, но в целом все сводится к этому). C очень похож на эту простоту.
C состоит в том, чтобы соединить алгебру с арифметикой.
C инкапсулирует основы сборки (язык процессора). Вероятно, это более верное утверждение, чем «Потому что типы данных и управляющие структуры, предоставляемые C, поддерживаются напрямую большинством компьютеров»
источник
Остерегайтесь вводящих в заблуждение сравнений
источник
Все основные типы данных и их операции на языке C могут быть реализованы с помощью одной или нескольких инструкций машинного языка без цикла - они напрямую поддерживаются (практически каждым) процессором.
Для нескольких популярных типов данных и операций с ними требуются десятки инструкций на машинном языке, или требуется повторение некоторого цикла времени выполнения, или и то, и другое.
Многие языки имеют специальный сокращенный синтаксис для таких типов и их операций - использование таких типов данных в C обычно требует ввода гораздо большего количества кода.
К таким типам данных и операциям относятся:
Все эти операции требуют десятков инструкций на машинном языке или повторения некоторого цикла выполнения почти на каждом процессоре.
Вот некоторые популярные управляющие структуры, которые также требуют десятков инструкций на машинном языке или циклов:
Независимо от того, написано ли оно на C или другом языке, когда программа манипулирует такими типами данных, ЦП должен в конечном итоге выполнить все инструкции, необходимые для управления этими типами данных. Эти инструкции часто содержатся в «библиотеке». Каждый язык программирования, даже C, имеет «библиотеку времени выполнения» для каждой платформы, которая по умолчанию включена в каждый исполняемый файл.
Большинство людей, пишущих компиляторы, помещают инструкции по управлению всеми типами данных, которые «встроены в язык», в свою библиотеку времени выполнения. Поскольку C не имеет каких-либо из перечисленных выше типов данных, операций и структур управления, встроенных в язык, ни один из них не включен в библиотеку времени выполнения C, что делает библиотеку времени выполнения C меньше, чем библиотека времени выполнения. time библиотеки других языков программирования, в которые встроено больше перечисленного выше материала.
Когда программист хочет, чтобы программа - на C или любом другом языке по его выбору - управляла другими типами данных, которые не «встроены в язык», этот программист обычно говорит компилятору включить дополнительные библиотеки в эту программу, а иногда (чтобы «избежать зависимостей») записывает еще одну реализацию этих операций прямо в программу.
источник
Какие встроенные типы данных
C
? Они такие вещи , какint
,char
,* int
,float
, массивы и т.д. ... Эти типы данных понимаются ЦП. ЦП знает, как работать с массивами, как разыменовать указатели и как выполнять арифметические операции с указателями, целыми числами и числами с плавающей запятой.Но когда вы переходите на языки программирования более высокого уровня, вы встраиваете абстрактные типы данных и более сложные конструкции. Например, посмотрите на огромный массив встроенных классов в языке программирования C ++. ЦП не понимает классы, объекты или абстрактные типы данных, поэтому среда выполнения C ++ устраняет разрыв между ЦП и языком. Это примеры типов данных, которые напрямую не поддерживаются большинством компьютеров.
источник
Это зависит от компьютера. На 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 не поддерживает даже с надстройкой, иcsv
(аcret
также в csv.c), которые сохраняют и восстанавливают регистры в стеке для управления вызовами и возвратами из функций.Вероятно, они также ссылались на свой выбор не поддерживать многие типы данных, которые напрямую не поддерживаются базовой машиной, в отличие от других современных языков, таких как FORTRAN, в которых семантика массивов не соответствовала поддержке базового указателя ЦП, как Массивы C. Тот факт, что массивы C всегда имеют нулевой индекс и всегда имеют известный размер во всех рангах, но первое означает, что нет необходимости хранить диапазоны индексов или размеры массивов, и нет необходимости иметь библиотечные функции времени выполнения для доступа к ним - компилятор может просто жестко закодировать необходимую арифметику указателя.
источник
Заявление просто означает, что структуры данных и управления в 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
формат, который согласуется с форматом с плавающей запятой, поддерживаемым процессором, не только потому, что они оба были согласованы, но потому, что существует независимый стандарт для этого представления.источник
Такие вещи, как
Списки Используются почти на всех функциональных языках.
Исключения .
Ассоциативные массивы (Карты) - включены, например, в PHP и Perl.
Сборка мусора .
Типы данных / управляющие структуры включены во многие языки, но напрямую не поддерживаются ЦП.
источник
Под непосредственной поддержкой следует понимать эффективное отображение на набор команд процессора.
Прямая поддержка целочисленных типов является правилом, за исключением длинных (могут потребоваться расширенные арифметические процедуры) и коротких размеров (может потребоваться маскирование).
Для прямой поддержки типов с плавающей запятой требуется наличие FPU.
Прямая поддержка битовых полей является исключительной.
Структуры и массивы требуют вычисления адреса, что в некоторой степени напрямую поддерживается.
Указатели всегда напрямую поддерживаются через косвенную адресацию.
goto / if / while / for / do напрямую поддерживаются безусловными / условными переходами.
Переключатель может напрямую поддерживаться, когда применяется таблица переходов.
Вызовы функций напрямую поддерживаются функциями стека.
источник