Сначала будет много про Си, тк Си++ - исходно расширение языка си: изначально он был как предпроцессор, который превращал его в си код, который потом компилировался - это был не отдельный язык, а надстройка над си. Потом C++ разошелся с си, некоторые вещи работают по-другому. Но в общем С++ можно считать надмножеством языка си, многое, что работает в си, работает и в ++.
Идея - языка C/C++.
Почему важно? - сильно влияет на проиходящие, стиль и т.д. Си относиться к одним из самых старых яп которые живы: старше си только фортран. Фортран создавался как язык для научных вычеслений, напрмер в нём легко работать с матрицами и т.д. А на си удобнее работать с системными функциями. Фортран часто используется в вычеслительных серверах. На нём написано много ПО, по этому яп до сих пор жив. Отметим, что ЯП бывают разные, так, например, Паскаль и вовсе создавался для обучения.
-Для чего создавался си?
-Системными программистами для себя. Программистам приходилась писать всё на ассеблере каждый раз на новую железку, чтобы так не делать - придумали си. Но достаточно низкоуровневый, для быстрых операций.
Си устроен синтаксически очень кратко. В паскале/java Integer начинает бесить пока я это пишу. [развить бы мысль… примером или ещё какой-нибудь конкретикой]
Язык Си очень доверяет программисту есть очень много разных примеров
- Чем функция отличается от процедуры с идеологической точки зрения?
-функция возвращает что-то, а процедура нет
В других языках (паскаль) есть разделение на функции и процедуры, и важно, что если в паскале вы вызываете функцию, то вы обязаны значение этой функции использовать. В си существуют только функции и никто не заставляет значение возврата использовать.
Наверное, слышали, что на си легко выстрелить себе в ногу. Си не будет держать тебя за ручку, он подумает, что ты умней, ты знаешь, что делаешь. Это и хорошо и плохо. Плохо - из-за порога вхождения: требуется выше калификация. (на Java нужна квалификация ниже; си и особенно си++ требуется квалификация больше, чтобы не натворить глупостей) С другой стороны, то, что вам язык больше доверяет - позволяет вам больше сделать. Некоторые действия на всяких высокуровневых ЯП надо ещё изголиться осуществить или просто нельзя, а цена проверок, оберегающих незадачливогопрограммиста - скорость (на java программы переодически откровенно лагают). Чем более высокоуровневый язык, тем больше абстракций и больше стоимость абстракций, а чем низкоуровневее - тем больше извращений, кхм, реизобретений велосипеда и технических сложностей при написании, но и больше процент вычеслительной мощности. (мин количество абстракций)
Если вас интересует в первую очередь скорость разработки и возможность использования низкоквалифицированного персонала - С/C++ плохой вариант
Если вас интересует в первую очередь скорость работы, и вы готовы потратить больше времени на отладку, чтобы сэкономить на железке - С/C++, вероятно, хорошая идея.
Так:
Java - C/C++ с отрезанными указателями
C# - java От майкрософт
Ещё важная особенность си, в отличие от Rust, в си есть спецификация, почему rust, считается, пока не дошедшим до серьёзного состояния - нет спецификации: пока нигде не описанно, как точно он должен работать, как оно должно выполняться - компилятор один (как работает - так и должно) (это не очень хорошо - потому что есть багги)
У C/C++ есть великое множество компиляторов, его для подмножества си можно написать за 3 дня, но у такого компилятора написанного студентом есть огромная-огромная раница с крупными.
-Чем (крупный) компилятор отличается от просто компиляторов?
- это оптимизирующй компилятор (это очень сложно) (генерация машинного кода будет быстрой и компактной [низкозатратной])
- выдают очень хорошие ошибки/warnin'ги (не поймёте, пока сами не напишите)
Когда компилятор проходит ваш код и там простая ошибка - забыта ;
, то он ломается это не в том месте, а где-то далеко и вообще не очевидным образом, и догадаться про ;
- не тривиально. То, что выдаются хорошие и точные ошибки - это огромная огромная работа прогроммистов компиляторов.
Крупные компиляторы C/C++ (3,5 независимых реализаций):
Когда народ пишет компилятор - они начинают писать суперфичи, которых нет в стандарте используемые только при компиляции этим компилятором. Могут включить в след релизе стандарта.
Стандарт - та часть языка, которая работает везде!
Суперфичи - то, что работает с конкретным компилятором (не стандартная), больше всего проблем с GCC, например:
#include <bits/stdc++. h>
(подключить все стандартные библиотеки(хедеры))
Все настолько плохо, что однажды когда пытался скомпилировать программу clang он выдал ошибку: This feature gcc will never be supported
(раньше Visual Studio [VS] грешила этим.) Поддержка стандартов тоже очень плавает - VS откровенно не хотели поддерживать новые стандарты именно C.
Идея C/C++ - быть более-менее переносимым. С точки зрения пероносимости - Си гораздо круче java! (если на каком-то железе есть java, то с ооогромной вероятность есть компилятор си) Он в отличие от Java очень хорошо держит обратную совместимоть (Java и мобильная java - разные вещи).
Си очень хорошо держит обратную совместимость. Был случай, когда пытался собрать программу на java, если слишком старая версия - этих фукнций нет, если слишком новая - эти функции задипликэйтили, то есть их уже нет. Нужно было двоичным посиком искать нужную версию java. Да и десктоп версия и мобильная версия java - совершенно разные вещи.
СИ довольно консервативен, В ++ все время выходят бешеные новые стандарты, где добавлена куча новых фичей, где 2 следующих стандартов их пытаются пофиксить, потом дипликэйтят[орусевшее слово какое-то, знать бы хоть какое слово заимствовали], а в следующем вводят новую вариацию на тему того же самого.
В C за всё время развития задиплекэйтили только одну единственную функцию - gets()
, потому что она читает в буфер неопределенные размеры, и легко получить buffer overflow
(в 11 стандарте).
Исходно в Си не было bool (из-за бесполезности), в C++ был сразу, в си добавили в виде _Bool
, тк кто-то мог использовать bool
в качестве имени переменной, а слова начинающиеся с _ зарезервированны под будущие версии языка. (#include <stdbool.h>
- будет bool
)
(std - сокр. от standart)
-Чем документация отличается от спецификации?
Документация - описание разной степени подробности
Спецификация - требование, как оно должно работать
Стандарт описан очень обтекаемо, тк железо работало по разному. Стандарт был рассчитан на то, чтобы эффективно компилировать программы, чтобы после компиляции программа наиболее эффективно работала на разном железе. Если железо работает по-разному, а стандарт чёткий - железо, которое работает не так, должно в этом месте городить кучу костылей, чтобы получить то поведение, которое требует стандарт. Многие части стандарта си Undefined Behavior или Implementation-defined behavior не потому, что не могли норм написать стандарт, а потому что железо работало в этом месте по-разному. Но эти вещи в большинсте устарели. (первый международный стандарт си выпустили в 90, придумали в 72)
Современное железо гораздо более унифицированно, в отличие от времени когда писался первый стандарт си, например: представление целого числа - дополнение до 2х (почти везде) (в стандарте си 23 скорее всего это признают) [поясните пж]
-Что представляет из себя компилятор?
-Компиляция - перевод с одного ЯП на другой (обычно на машинный код)
Про чистый си можно порекомендовать книжку Керниган Ричи - язык программирования СИ, от создателя языка, есть переводы на русский, но есть и плохие переводы. Если посмотреть старые старые издания этой книжки - там весь код написан заглавными, тк в те далекие времени в компьютерах не было маленьких букв. Не разделяли на маленькие и большие. (сейчас не прокатит, си - регистрозависиммый язык)
Программа на си состоит из функций, и весь код распредлен между функциями, они равноправны, и не бывает вложенных функций (а в gcc бывает), едиственная выделенная функция - main, с неё начинается программа
функция - тип-возвр-значения имя (аргументы)
С точки зрения Кернигана и Ричи, пустое место в типе возвр значения яснее всего указывает на то, что функция возвращает int.
void
- специальный тип, который можно поставить в то место где тип требуется, но никакого типа по факту нет
main
может принимать аргументы
переменную типа void
не создать
процедура - это void
функция
f(void) и в С, и в С++ означает, что функция не имеет параметров, а f() в С++ означает то же самое, а в некоторых стандартах С — что функция имеет неизвестное число параметров это относится к тем временам, когда типы аргументов объявлялись не в прототипе функции, а в реализации, т.е.
int main(void)
- заголовок функции
{ }
- тело фунции (или чего либо другого)
Если функция описана возвращать что-то кроме void
- она должна заканчиваться строчкой return somthing;
, причем somthing
должно быть того же типа, что и мы обещали в обьявлении функции.
-А куда мы этот 0 (в примере) возвращаем?
-Программу запускает операц система, и по завершению процесса - у процесса есть код завершения, стандарт соглашение что если программа заканчивает успешно - она возвращает 0, если нет, какую-то цифру означ ошибку.
return
можно писать в функции void
(чтобы досрочно выйти из функции)
int void return
и т.д. - ключевые слова, так например нельзя назвать переменную.
Но, например, функция вывода в консоль printf
- не входит в синтаксис языка, это функция стандартной библиотеки.
То есть это набор функций который по умолчанию поставляется вместе с компилятором Си, он у вас практически наверняка есть за исключением хитрых систем, например у вашей кофеварки нет экрана, не очень по стандарту но бывает.
-как обьянить что такая функция существует?
-самый простой вариант - реализовать эту функцию до обращения. Тогда компилятор сразу будет знать о ней
-обьявить её
Так не скомпилируется (ниже)
-Что нужно сделать, чтобы оно работало?
-Нужно сообщить компилятору, что функция
sum
существует, по факту написать прототип функции (обьявить функцию)
В этом случае можно расположить реализацию функции после вызова.
Но тогда нам нужно прописывать прототипы всех стандартных функций, это слишком долго и муторно, по этоиу мы подлючаем стандартные заголовочные файлы, в который и прописаны это прототипы. например для ввода/вывода: #include <stdio.h>
-обязательно ли писать имена аргументов в обьявлении?
-нет.
Как можно написать реализацию функции в другом файле?
sum.c
main.c
Когда есть прототип, функция может быть реализованная не только после, но и в другом файле
ИЛИ
sum.c
sum.h
main.c
При компиляции #include <>
подключаемый файл сначала ищется в стандартных каталогах, а потом в текущей рабочей папке
При компиляции #include ""
подключаемый файл ищется только в текущем рабочем каталоге
текущая рабочая папка, в данном случае, откуда запустили компилятор
текущая рабочая папка в остальных случаях (в других контекстах, например при открытие файла) - место откуда запустили ваш
.exe
(исполяемый файл,.exe
- это расширение исполняемых файлов подwindows
)
Как всё это работает? Обычно, как во многих языках, процесс компиляции состоит из двух этапов:
.o
или .obj
обычно, компилируются только файлы .c
и независимо друг от друга..exe
(если win), он собирает всю программу вместе, и вставляет нужные куски из других файлов на место недопереведенных.Компиляция начинается с предпроцессора
.obj
- полуфабрикат, где часть кода переведенна в машинный код, а кое что ещё недопереведенно, например недопереведенно будет вызов функции sum
в main
в последнем примере выше.
Если мы обьявим функцию но не реализуем её нигде? Программа скомпилируется, но не слинкуется. (или окажется несколько функций с одинаковым именем в нескольких файлах, должны совпасть имена функций и типы принимаемые аргументы) (или если нет main
)
На самом деле выполнение работы начинается не с main
, а с runtime
, служебных си'шных функций, которые инициализуют окружение (например парсят аргументы main
, иниц глобальные переменные), а он уже вызывает main
.
Старт программы - недра системы->runtime C->main Точка входа - main
Линковка может работать с разными языками, ей вообще не важен язык.
-Что за бред, как может получиться, что программа на двух разных языках?
-Например, если мы используем внешние бинарные библиотеки (.dll)
-Во время линковки мы компилируем файлы из одной директории (которые лежат в одной папке)?
-Нет, во время линковки мы компилируем и собираем вместе то, что мы дали линковщику
.dll
- это тоже самое, что и .exe
, но без точки входа (что-то экспортировать), призваны для уменьшения времени компиляции, и для сокрытия исходного кода.
Рекомендация - читать документацию на английском.
Программисты пишут документацию на английском, на других языках её очень лень писать, и очень часто перевод документации - машинный, или каким-нибудь гуманитарием. Поэтому смысл в такой документации очень искажен.
(тоже самое про ru.wikipedia.org)
Девиз Яндекса - найдется всё (зарос рекламой и ведёт себя неприлично) Девиз Google - найдётся то, что ты искал
Link Time Code Generation
Смысл - оптимизировать всю программу. Компилятор видит каждый файл программы НЕзависимо, поэтому что-то соптимизировать между файлами просто принципиально не может. Если мы хотим дать возможность соптимизировать программу целиком - применяется специальный хитрый вариант компиляции, когда .obj
дает не машинный код, а промежуточный код этого компилятора, а потом линкер на этапе линковки снова вызывает компилятор, уже для всего общего промежуточного кода, и он его докомпилирует до машинного. Для этого линкер и компилятор должны 'дружить'.
Перегрузки функций в си - нет. В си++ сделан вид, что есть. В си++ можно создавать функции с одинаковым именем и разными аргументами (разным количеством аргументов).
Name Mangling
Проблема в том, что на самом деле имя у этих функций не одинаковое, и когда всё это компилируется в .obj
линкер без понятия плюсы там или нет и т.д. Если вы линкеру дадите несколько функций с одинаковым именем - он вас отругает. Поэтому C++ этим функциям даёт неодинаковые имена, они к имени функции добавляют всякие @...
и т.д. где этими магическими символами кодируют типы аргументов этих функции, то есть на самом деле имена этих функций уникальные, просто с добавкой.
Поэтому если мы создадим один файл .c, а другой .cpp то у нас не скомпилируется, тк у одной и той же функции будет раные имена (в си++ туда добавятся магические символы) и линковщиу не сможет найти реализацию вызываемой функции.
Это лечится, если сказать компилятору C++ не трогать имя, я хочу настоящее имя функции - extern "C" void f (int x);
, но тогда перегрузить функцию f
не получится.
В стандарте Name Mangling не описан, и предугадать его не получится никак, тк даже один компилятор в разных версиях использует разные генерации этих имен
(совместимость на двоичном уровне у ++ гораздо хуже чем у Си)
Мы не хотим компилировать разные части программы разными компиляторами
Си выбран стыкующим языком, если мы хотим пристыковать код на одномом языке к коду на другом языке - то эта штука практически всегда происходит через си'шный интерфейс (либо быть си, либо через стыковку по правилам си)
Full - C/C++, 1 лекция, 12.02.2022, Скаков