Как язык расширяется?

Я изучаю С++, и я только начал изучать некоторые возможности Qt для кодирования графических программ. Я задал себе следующий вопрос:

Как С++, у которого ранее не было синтаксиса, способного запрашивать ОС для окна или способ общения через сети (с API, которые я не полностью понимаю, признаюсь), вдруг получают такие возможности через библиотеки, написанные на языке С++? Все это кажется мне ужасно крутым. Какие инструкции на С++ вы могли бы найти в этих библиотеках?

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

Ответ 1

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

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

Ответ 2

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

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

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

Ответ 3

C и С++ имеют 2 свойства, которые допускают всю эту расширяемость, о которой говорит OP.

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

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

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

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

Существует еще один уровень API, который намного полезнее системного вызова. Возьмем, к примеру, malloc. Мало того, что это вызовет систему для получения больших блоков памяти, но будет управлять этой памятью, делая всю книгу в том, что происходит.

API Win32 объединяет некоторые графические функции с общим набором виджета платформы. Qt делает это немного дальше, обернув API Win32 (или X Windows) кросс-платформенным способом.

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

Ответ 4

Языки (например, C++11) являются спецификациями на бумаге, обычно написанными на английском языке. Просмотрите последний проект С++ 11 (или приобретите дорогостоящую окончательную спецификацию от поставщика ISO).

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

Ваш общий проект С++ работает над некоторой операционной системой и общается с ней (используя некоторый код конкретной реализации, часто в некоторой системной библиотеке). Как правило, это сообщение осуществляется через системные вызовы. Посмотрите пример в syscalls (2) для списка системных вызовов, доступных в ядре Linux.

С точки зрения приложения, syscall является элементарной машинной инструкцией типа SYSENTER на x86-64 с некоторыми соглашениями (ABI)

На моем рабочем столе Linux библиотеки Qt находятся выше X11 клиентских библиотек, взаимодействующих с сервером X11 Xorg через X протоколы Windows.

В Linux используйте ldd в вашем исполняемом файле, чтобы увидеть (длинный) список зависимостей от библиотек. Используйте pmap в текущем процессе, чтобы посмотреть, какие из них загружены во время выполнения. BTW, в Linux ваше приложение, вероятно, использует только бесплатное программное обеспечение, вы можете изучить его исходный код (от Qt, до Xlib, libc,... ядро), чтобы понять, что еще происходит

Ответ 5

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

Системные вызовы - это низкоуровневый способ использования мощностей операционной системы, но могут быть сложными и громоздкими в использовании, поэтому их часто "обертывают" в API, так что вам не нужно иметь дело с ними непосредственно. Но под ним практически все, что вы делаете, которое связано с ресурсами, связанными с O/S, будет использовать системные вызовы, включая печать, создание сетей и сокетов и т.д.

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

Ответ 6

Хороший вопрос. Каждый новый разработчик C или С++ имеет это в виду. Я принимаю стандартную машину x86 для остальной части этого сообщения. Если вы используете компилятор Microsoft С++, откройте свой блокнот и введите его (назовите файл Test.c)
int main(int argc, char **argv)
{
   return 0
}

И теперь скомпилируйте этот файл (с помощью командной строки разработчика) cl Test.c/FaTest.asm

Теперь откройте Test.asm в своем блокноте. Что вы видите, это переведенный код - C/С++ переводится на ассемблер. Вы получаете подсказку?

_main   PROC
    push    ebp
    mov ebp, esp
    xor eax, eax
    pop ebp
    ret 0
_main   ENDP

Программы C/С++ предназначены для работы на металле. Это означает, что они имеют доступ к аппаратным средствам более низкого уровня, что упрощает использование возможностей аппаратного обеспечения. Скажем, я собираюсь написать библиотеку C getch() на машине x86.

В зависимости от ассемблера я бы напечатал что-то таким образом:

_getch proc 
   xor AH, AH
   int 16h
   ;AL contains the keycode (AX is already there - so just return)
ret

Я запускаю его с помощью ассемблера и создаю .OBJ - Назовите его getch.obj.

Затем я пишу программу C (я не #include что-либо)

extern char getch();

void main(int, char **)
{
  getch();
}

Теперь назовите этот файл - GetChTest.c. Скомпилируйте этот файл, передав getch.obj. (Или скомпилируйте отдельно для .obj и LINK GetChTest.Obj и getch.Obj вместе для создания GetChTest.exe).

Запустите GetChTest.exe, и вы увидите, что он ждет ввода клавиатуры.

Программирование на C/С++ - это не только язык. Чтобы быть хорошим программистом на C/С++, вам нужно хорошо понимать тип машины, на которой он работает. Вам нужно будет знать, как обрабатывается управление памятью, как структурированы регистры и т.д. Возможно, вам не нужна вся эта информация для регулярного программирования, но они вам очень помогли бы. Помимо базовых знаний об оборудовании, это, безусловно, помогает, если вы понимаете, как работает компилятор (т.е. Как он переводит) - что может помочь вам настроить ваш код по мере необходимости. Это интересный пакет!

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

Не обязательно всегда ссылаться на Assembler. Я упомянул об этом, потому что думал, что это поможет вам лучше понять. В основном, большинство таких вызовов библиотеки используют системные вызовы /API, предоставляемые операционной системой (ОС, в свою очередь, взаимодействует с оборудованием).

Ответ 7

Как С++... вдруг получает такие возможности через библиотеки написанные на языке С++?

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

Считайте, что вы пишете такую ​​функцию

void addExclamation(std::string &str)
{
    str.push_back('!');
}

Теперь, если вы включите этот файл, вы можете написать addExclamation(myVeryOwnString);. Теперь вы можете спросить: "Как С++ внезапно получил возможность добавлять восклицательные знаки к строке?" Ответ прост: вы написали функцию, чтобы сделать это, тогда вы ее назвали.

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

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

Ответ 8

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

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

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

Ответ 9

Нет необходимости в специальном синтаксисе для создания окон. Все, что требуется, - это то, что ОС предоставляет API для создания окон. Такой API состоит из простых вызовов функций, для которых С++ обеспечивает синтаксис.

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

Следует также упомянуть, что Qt - плохой пример, так как он использует так называемый мета-компилятор для расширения синтаксиса С++. Однако это не связано с возможностью вызова API-интерфейсов, предоставляемых ОС, для фактического рисования или создания окон.

Ответ 10

Во-первых, там немного непонятно, я думаю,

Как С++, у которого ранее не было синтаксиса, способного запрашивать ОС для окна или способ общения через сети

Нет синтаксиса для выполнения операций ОС. Это вопрос о семантике.

внезапно получить такие возможности через библиотеки, написанные на самих С++

Ну, операционная система написана в основном на C. Вы можете использовать разделяемые библиотеки (так, dll) для вызова внешнего кода. Кроме того, код операционной системы может регистрировать системные процедуры в syscalls * или прерываниях, которые вы можете вызывать, используя сборку. Эти общие библиотеки часто просто заставляют эту систему обращаться за вами, поэтому вы избавлены от использования встроенной сборки.

Вот хороший учебник по этому поводу: http://www.win.tue.nl/~aeb/linux/lk/lk-4.html
Это для Linux, но принципы одинаковы.

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

Ответ 11

В попытке дать немного другое представление другим ответам, я отвечу вот так.

(Отказ от ответственности: я немного упрощаю ситуацию, ситуация, которую я даю, является чисто гипотетической и написана как средство демонстрации концепций, а не на 100% верна жизни).

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

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

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

Итак, много месяцев спустя, и у вас есть все функции вашей ОС. Пользователь может вызывать функции для создания окон и рисования на них, они могут создавать потоки и всевозможные замечательные вещи. Здесь проблема, однако, ваши функции ОС будут отличаться от функций Linux или функций Windows. Поэтому вы решаете, что вам нужно предоставить пользователю стандартный интерфейс, чтобы они могли писать переносимый код. Здесь находится QT.

Как вы почти наверняка знаете, QT имеет множество полезных классов и функций для выполнения тех операций, которые делают операционные системы, но таким образом, который не зависит от базовой операционной системы. Способ, которым это работает, заключается в том, что QT предоставляет классы и функции, которые являются едиными в том, как они отображаются пользователю, но код, лежащий в основе функций, отличается для каждой операционной системы. Например, QT QApplication:: closeAllWindows() фактически вызовет функцию закрытия специализированного окна операционной системы в зависимости от используемой версии. В Windows это скорее всего вызовет CloseWindow (hwnd), тогда как в ОС с помощью X Window System он потенциально может вызвать XDestroyWindow (отображение, окно).

Как видно, операционная система имеет много уровней, каждая из которых должна взаимодействовать через интерфейсы многих разновидностей. Есть много аспектов, которые я даже не затронул, но объяснить их все займет очень много времени. Если вас больше интересует внутренняя работа операционных систем, я рекомендую проверить ОС dev wiki.

Имейте в виду, что причина, по которой многие операционные системы предпочитают выставлять интерфейсы на C/С++, заключается в том, что они компилируются в машинный код, они позволяют смешивать команды сборки с их собственным кодом, и они обеспечивают большую степень свободы программист.

Опять же, здесь многое происходит. Я хотел бы пояснить, как библиотеки, такие как .so и .dll, не должны быть записаны на C/С++ и могут быть написаны на сборке или на других языках, но я чувствую, что если я добавлю больше, напишите целую статью, и я бы очень хотел, чтобы у меня не было сайта для ее размещения.

Ответ 12

Когда вы пытаетесь нарисовать что-то на экране, ваш код вызывает другой фрагмент кода, который вызывает какой-то другой код (и т.д.), пока, наконец, не появится "системный вызов", который является специальной инструкцией, которую может выполнять ЦПУ, Эти инструкции могут быть либо записаны в сборке, либо могут быть написаны на С++, если компилятор поддерживает их "intrinsics" (которые являются функциями, которые компилятор обрабатывает "специально", путем преобразования их в специальный код, который может понять ЦП). Их задача - сказать операционной системе что-то сделать.

Когда происходит системный вызов, вызывается функция, вызывающая другую функцию (и т.д.), пока, наконец, драйверу дисплея не потребуется нарисовать что-то на экране. В этот момент драйвер дисплея смотрит на конкретный регион в физической памяти, который на самом деле не является памятью, а скорее представляет собой диапазон адресов, который можно записать так, как если бы это была память. Вместо этого, однако, запись в этот диапазон адресов заставляет графическое оборудование перехватывать запись в память и рисовать что-то на экране.
Запись в эту область памяти - это то, что может быть закодировано на С++, поскольку на стороне программного обеспечения это обычный доступ к памяти. Это просто, что аппаратное обеспечение обрабатывает его по-другому.
Так что действительно базовое объяснение того, как оно может работать.

Ответ 13

Ваша программа на С++ использует библиотеку Qt (также закодированную на С++). Библиотека Qt будет использовать Windows CreateWindowEx (которая была закодирована в C внутри kernel32.dll). Или под Linux он может использовать Xlib (также закодированный на C), но он также может посылать необработанные байты, которые в X-протоколе "Пожалуйста, создайте для меня окно".

Связано с вашим catch-22 вопросом является историческая заметка о том, что "первый компилятор С++ был написан на С++", хотя на самом деле это был C с несколькими понятиями С++, достаточно, чтобы он мог скомпилировать первую версию, которая затем могла бы скомпилировать себя.

Аналогично, компилятор GCC использует расширения GCC: он сначала компилируется в версию, а затем используется для перекомпиляции. (инструкции по сборке GCC)

Ответ 14

Как я вижу вопрос, на самом деле это вопрос компилятора.

Посмотрите на это таким образом, вы пишете фрагмент кода в Assembly (вы можете сделать это на любом языке), который переводит ваш новый язык, который вы хотите вызвать Z ++ в Assembly, для простоты позволяет называть его компилятором (это компилятор).

Теперь вы даете этому компилятору некоторые базовые функции, чтобы вы могли писать int, string, массивы и т.д. На самом деле вы даете ему достаточно возможностей, чтобы вы могли написать сам компилятор в Z ++. и теперь у вас есть компилятор для Z ++, написанный на Z ++, довольно аккуратный.

Что еще круче, так это то, что теперь вы можете добавлять способности к этому компилятору с помощью возможностей, которые у него уже есть, тем самым расширяя язык Z ++ новыми функциями, используя предыдущие функции

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

Ответ 15

Аппаратное обеспечение позволяет это осуществить. Вы можете думать о графической памяти как о большом массиве (состоящем из каждого пикселя на экране). Чтобы рисовать на экране, вы можете записать эту память с помощью С++ или любого языка, который обеспечивает прямой доступ к этой памяти. Эта память просто доступна или доступна на видеокарте.

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