Я часто слышал, что для понимания работы компьютера люди предлагают изучать C. Это хорошая мысль? Вы уверены? Сразу изложу выводы статьи, просто для абсолютной ясности:

  • C — это не то, «как работает компьютер».
  • Не думаю, что большинство людей говорят буквально, так что это неважно.
  • Понимание контекста означает, что учить С по этой причине всё еще может иметь смысл, в зависимости от ваших целей.

Я планирую написать ещё две статьи с более подробным объяснением выводов, но этого уже достаточно. Добавлю сюда ссылки, когда статьи выйдут.

Я часто слышал от людей такое:

Изучая C, вы можете понять, как работают компьютеры.

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

Прежде чем мы действительно начнём, хотел бы сказать ещё кое-что: если хотите изучить C, то изучайте! Учиться — это здорово. Изучение C стало очень важным для моего понимания вычислительной техники и моей карьеры. Изучение этого языка и его места в истории языка программирования сделает вас лучшим программистом. Вам не нужно никакое оправдание. Изучайте вещи просто ради обучения. Эта статья призвана стать ориентиром, чтобы разобраться в истине, она не обсуждает, нужно или нет изучать С.

Прежде всего, кому вообще рекомендуется эта идея. Если вы пытаетесь «узнать, как работают компьютеры», то само собой разумеется, что вы в настоящее время этого не понимаете. Какие программисты не понимают, как работают компьютеры? Я в основном видел, что это чувство исходит от людей, которые в основном программируют на динамически типизированных «скриптовых» языках, таких как Ruby, Python или JavaScript. Они якобы «не знают, как работают компьютеры», потому что эти языки работают внутри виртуальной машины, где имеет значение только семантика виртуальной машины. В конце концов, вся идея виртуальной машины заключается в обеспечении переносимости. Цель в том, чтобы не зависеть от оборудования, на котором работает VM.

Есть только одна проблема: C тоже работает внутри виртуальной машины.

Абстрактная машина C


Из спецификации C99, раздел 5.1.2.3, «Выполнение программы»:

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

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

Еще одно замечание: здесь я выбрал C99, который не является последним стандартом C. Почему? Ну, в MSVC есть… интересная поддержка языка С, и в наши дни я пользователь Windows. Да, вы можете запускать clang и gcc под Windows. Между C89, C99 и C11 не такая большая разница в отношении того, о чём мы говорим. В какой-то момент приходится выбирать. Версия, которую я здесь упомянул, включает в себя некоторые правки к первоначальной спецификации.

Возможно, в разговорах о C вы слышали ещё одну фразу: «C — переносимый ассемблер». Если задуматься об этой фразе, то вы поймёте, что если это правда, то C не может соответствовать работе компьютера: существует много разных компьютеров с разной архитектурой. Если C похож на ассемблер, который работает на разных компьютерах с разными архитектурами, то он не может одновременно функционировать в точности так, как каждый из этих компьютеров. Он должен прятать детали, иначе не будет переносимым!

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

Отступление: почему люди заблуждаются?


Могу рассказать только о своём опыте, хотя наверняка он не уникален.

Я изучил GW-BASIC, потом С, потом С++, потом Java. Я слышал о Java до того, как начал писать на ней примерно в 1999 году, через четыре года после её появления. Маркетинг в то время активно противопоставлял Java и C++, он сосредоточился на JVM как платформе, и на том, что модель машины отличает её от C++, и, следовательно, C. Sun Microsystems больше не существует, но зеркало пресс-релиза напоминает нам:

Приложения на Java не зависят от платформы; нужно лишь портировать виртуальную машину Java на каждую платформу. Она действует как интерпретатор между компьютером пользователя и Java-приложением. Приложение, написанное в среде Java, может работать в любом месте, избавляя от необходимости переноса приложений на несколько платформ.

Главным девизом было «Пиши один раз, запускай везде». Эти два предложения стали тем, как я (и многие другие) пришёл к пониманию Java, и как она отличается от C++. У Java есть интерпретатор, виртуальная машина Java. В C++ нет виртуальной машины.

С таким мощным маркетингом «виртуальная машина» в умах многих людей стала синонимом «большой среды выполнения и/или интерпретатора». Языки без этой функции были слишком привязаны к конкретному компьютеру и требовали портирования, поскольку не являются по-настоящему независимыми от платформы. Главной причиной существования Java было изменение этого недостатка C++.

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

Я лично считаю, что этот маркетинг 1995 года — причина, почему программисты до сих пор неправильно понимают природу C.

Так это утверждение ложно? Зачем Sun Microsystems тратить миллионы и миллионы долларов на пропаганду лжи? Если C тоже основан на абстрактной машине, которая предлагает переносимость между платформами, зачем нужна Java? Думаю, что это ключ к пониманию того, что люди действительно имеют ввиду, когда говорят «С — это то, как работает компьютер».

Что люди на самом деле имеют в виду?


Хотя C работает в контексте виртуальной машины, он по-прежнему значительно отличается от Java-подобных языков. Sun не врала. Чтобы понять, нужно знать историю С.

В 1969 году в Bell Labs была написана компьютерная операционная система на языке ассемблера. В 1970 году её окрестили UNIX. С течением времени Bell Labs покупала всё больше и больше новых компьютеров, включая PDP-11.

Когда пришло время портировать Unix на PDP-11, они решили использовать язык более высокого уровня, что было довольно радикальной идеей в то время. Представьте, что сегодня я вам скажу: «Я собираюсь написать ОС на Java» — вероятно, вы будете смеяться, хотя идея реализуема. Ситуация (в моём понимании, я тогда не жил) была примерно аналогичной. Рассматривался язык под названием B, но он не поддерживал некоторые функции, которые были у PDP-11, и поэтому они создали преемника, назвав его "C", поскольку это была следующая буква в алфавите.

Языка "A" не было; B стал преемником BCPL (Basic Combined Programming Language).

В 1972 году на PDP-11 написали первый компилятор C и одновременно переписали UNIX на C. Изначально о переносимости не думали, но C получил известность, так что компиляторы C портировали на другие системы.

В 1978 году вышло первое издание книги «Язык программирования С». Ласково именуемая «K&R», по именам её авторов, книга совсем не была похожа на спецификацию, но при этом достаточно подробно описывала язык, в результате чего другие тоже попытались написать компиляторы С. Позже эту «версию» будут называть «K&R C».

По мере распространения UNIX и C их обоих портировали на многие компьютеры. В 70-х и 80-х годах их аппаратная база непрерывно росла. Точно так же, как C создали, потому что B не поддерживал все функции PDP-11, многие компиляторы использовали расширения языка. Поскольку существовал только K&R, а не спецификация, то это считалось приемлемым, пока расширения были достаточно близки. К 1983 году отсутствие какой-либо стандартизации стало вызывать проблемы, поэтому в ANSI создали группу для подготовки спецификации. В 1989 году вышел стандарт C89, который иногда называется "ANSI C".

Спецификация C пыталась унифицировать эти разнообразные реализации на различном оборудовании. Таким образом, абстрактная машина C — это своего рода минимально возможная спецификация, которая позволила бы одному и тому же коду работать одинаково на всех платформах. Реализации C компилировались, а не интерпретировались, поэтому не было интерпретатора, поэтому не было "VM" в том смысле 1995 года. Однако программы на языке C пишутся на этом абстрактном несуществующем компьютере, а затем код преобразуется в ассемблер, специфичный для конкретного компьютера, на котором выполняется программа. Вы не могли полагаться на некоторые конкретные детали для написания переносимого кода на С. Это делает написание переносимого C очень сложным, так как вы, возможно, сделали специфичное для платформы предположение при написании начальной версии своего кода.

Это лучше всего иллюстрируется примером. Одним из основных типов данных в языке C является char, от слова «символ». Однако абстрактная машина C не определяет, сколько бит должно быть в char. Ну, определяет, но не числом; она определяет размер CHAR_BIT, который является константой. Раздел 5.2.4.2.1 спецификации:

Приведённые ниже значения должны быть заменены константными выражениями, подходящими или используемыми в директивах предобработки #if.… Значения в конкретных реализациях должны быть равны или больше по величине (абсолютное значение) тех, которые приведены здесь, с тем же знаком.

CHAR_BIT: 8

Другими словами, вы знаете, что char составляет не менее 8 бит, но реализации могут быть больше. Чтобы правильно кодировать «абстрактную машину C», в качестве размера при обработке char необходимо использовать CHAR_BIT вместо 8. Но это не какая-то функция интерпретатора, как мы думаем о виртуальных машинах; это свойство того, как компилятор переводит исходный код в машинный код.

Да, есть системы, где CHAR_BIT не 8.

Таким образом, эта «абстрактная машина», хотя технически и является той же идеей, что и виртуальная машина Java, скорее представляет собой конструкцию компиляции для управления компиляторами при создании ассемблерного кода, а не какой-то проверкой в рантайме или свойством. Эквивалентный тип в Java — это byte, который всегда составляет 8 бит, а на реализацию JVM возлагается задача, что делать на платформах, где байт больше. (Не уверен, работает ли JVM на любой из этих платформ, но именно так это должно функционировать). Абстрактная машина C создана как минимальная обёртка для различного «железа», а не в качестве какой-то платформы из цельной ткани, написанной в софте для вашего кода.

Таким образом, хотя Sun была технически не права, на практике они имеют в виду немного не то, что буквально говорят, и что они имеют в виду — верно. То же самое с фразой «Изучай C, чтобы понять, как работают компьютеры».

Изучай С, чтобы ЛУЧШЕ понять, как работают компьютеры


Что на самом деле люди имеют в виду? В контексте «должен ли рубист изучать C, чтобы понять, как работают компьютеры» — это совет снизиться «до уровня железа». То есть не только понять, как своя программа работает внутри виртуальной машины, но и как сочетание программы и VM работают в контексте самой машины.

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

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

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

По этой причине я думаю, что более точная версия этого утверждения будет «Изучая C, вы больше узнаете о том, как работают компьютеры». Я действительно думаю, что примерное знакомство с C полезно многим программистам, даже если они сами не пишут C. Знакомство с C также даст вам представление об истории развития нашей отрасли.

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

В программировании так много всего, чему стоит поучиться. Желаю вам успехов на этом пути.

Комментарии (98)


  1. Goron_Dekar
    19.10.2018 12:17

    Очень нехватает в этой статье конкретезации того, о каком компьютере идёт речь.
    Если о тех, которых большинство, то С самое близкое к тому, как они работают. Ну нет у большинства arm'ов и Pic'ов ни векторизации, ни предиктора.


    1. LexB
      19.10.2018 12:27
      +1

      Смотря что значит «понимать как работает компьютер», если совсем понимать, то начинать стоит с ассемблера.


      1. Goron_Dekar
        19.10.2018 12:54
        -1

        Ассемблер у всех компьютеров разный. С один.


        1. shiru8bit
          19.10.2018 13:33

          C тоже бывает разный. Разные стандарты, особенно до C89, разные ограничения, разные библиотеки. На одной платформе нет stdlib вообще, на другой нет динамического выделения памяти, на третьей нет float, разные размеры переменных, на древностях нет const, enum, и так далее.

          Довольно интересные ощущения возникают, если изучить C на чём-то современнее, чем Turbo C, а потом увидеть старообрядный код, типа:

          int foo(x)
            int x;
            { int y;
                 y = x * x;
                 return y;
            }


          1. LynXzp
            19.10.2018 14:08
            +1

            Или так:

            foo(x,z)
              char x;
              { int y;
                   y = x * z;
                   return y;
              }


            1. axe_chita
              19.10.2018 18:19

              Не покатит код, компилятор выдаст ошибку необъявленная переменная Z, плюс вывесит предупреждение необходимо приведение типов


              1. LynXzp
                19.10.2018 18:32

                Ну я же наверное знал что писал, не просто так выдумал? (Обратное тоже возможно, но как-то маловероятно на хабре) Есть такое правило «int по умолчанию» для старого С, функции по умолчанию возвращают int, даже если нет return (п.9), параметры по умолчанию int. Компилирую в gcc 6.4.0 без опций:

                tmp.c:1:1: warning: return type defaults to 'int' [-Wimplicit-int]
                tmp.c:1:1: warning: type of 'z' defaults to 'int' [-Wimplicit-int]
                Предупреждения есть, и было бы странно если бы их не было, но код компилируется и выполняется. Clang 6.0 и 7.0 аналогично, даже не смотря на то что его вообще не было когда такой синтаксис использовался.


                1. axe_chita
                  19.10.2018 20:10
                  -1

                  Естественно функция должна возвращать значение, иначе какая она тогда функция? возвращаемый тип void и был добавлен стандарт ANSI C и во многих компиляторах вышедших незадолго до его принятия делали такую великолепную заглушку #define void int чтобы сделать текст программы более совместимым.
                  На на счет использования необъявленной переменной, и без ошибки при компиляции… Ну это какойто BASIC, причем до 90-х годов, если не до 80-х. Жуть и дичь.


                  1. LynXzp
                    19.10.2018 20:15

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

                    > BASIC до 90-х
                    Или современный php. Но в моем коде нет необъявленной переменной, z это параметр функции, он объявлен. Просто так не объявлять переменную нельзя.


                    1. axe_chita
                      20.10.2018 19:39
                      -1

                      Нормальная функция, мы же не в рамках паскаля говорим с его процедурами.

                      [Зануда моде он]
                      Фу?нкция в программировании — фрагмент программного кода (подпрограмма), к которому можно обратиться из другого места программы.

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

                      В языках программирования высокого уровня используется два типа подпрограмм: процедуры и функции.

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

                      Функция_(программирование)
                      Подпрограмма
                      [Зануда моде офф]

                      Или современный php. Но в моем коде нет необъявленной переменной, z это параметр функции, он объявлен. Просто так не объявлять переменную нельзя.

                      В вашем коде z это параметр/переменная функции, и тип её НЕОПРЕДЕЛЕН, максимум что мы можем сказать про неё что ее класс хранения auto и это все. Возможно в ANSI C можно былобы написать определение функции int foo(int x,z), но для K&R данный выкрутас должен быть ошибкой. И это ошибка должна ловится компилятором, в ином случае авторов компилятора необходимо садить за стол, заставлять ложить руки на крышку стола, и с оттягом длинной деревянной линейкой по пальцам охаживать, приговаривая «что необъявленно, того не существует, нет никакого правила (по умолчанию), это ересь — не плоди её». Если вам такой метод кажется жестоким, могу предложить ремень как инструмент патча.


                  1. vvzvlad
                    19.10.2018 22:03

                    Естественно функция должна возвращать значение, иначе какая она тогда функция?

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


                    1. 0xd34df00d
                      20.10.2018 06:57

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


                      Либо это функция, которая никогда не возвращает управление (abort, например).


                      1. Gryphon88
                        20.10.2018 17:15

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


                        1. 0xd34df00d
                          20.10.2018 17:44
                          -1

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

                          Это вопрос другого уровня абстракции.


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

                          А это более интересный вопрос, но тоже легко решаемый с точки зрения семантики языка.


                          1. Gryphon88
                            20.10.2018 19:54

                            Честно говоря, не видел ФП на микроконтроллерах. Ниже «пионерский» код отправки байта через аппаратный UART на AVR. Тут мы читаем 2 регистра состояния (глобальное состояние) и пишем в регистр без кода возврата (грязная функция).

                            void SendByte(char byte)
                            {
                            while(!(UCSRA & (1<<UDRE))); /*Ждём опустошения регистра данных*/
                            UDR=byte; /*Кладём байт в регистр данных*/
                            }
                            Как тут можно уйти от глобального состояния и сайд-эффектов? Я не троллю, но правда не понимаю, как мешать IO с ФП.


                            1. 0xd34df00d
                              20.10.2018 20:38
                              -1

                              А не надо от него уходить, его надо учитывать (и сделать неглобальным).


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


                              readDataRegister :: IO Byte
                              readDataRegister = ...
                              
                              sendByte :: Byte -> IO ()
                              sendByte byte = do
                                iterateUntil (== 0) readDataRegister
                                writeByte udr byte

                              Функция имеет сайд-эффекты, функция читает и пишет во внешний мир, функция имеет тип IO smth.


                              1. Gryphon88
                                20.10.2018 21:47

                                Насколько я понимаю, работа с сетью на компьютере должна отличаться от работа с вводом-выводом на МК. Сетевая карта, получив пакет, дёргает прерывание и в нём перекладывает пакет в собственный буфер, а чаще в память, откуда пользовательская программа данные и берёт. В МК предотвращение «застревания» данных (а иногда и сброс флагов прерываний) лежит на программисте. Т.е. мы имеем ситуацию, когда ресурс может модифицироваться как программно (из main loop или обработчика прерывания), так и чисто аппаратно (из внешнего мира или при вызове другого прерывания, если вложенные прерывания запрещены). Нет никакой гарантии, что в IO smth мы вообще попадём. Тут вопрос, как не тащить в обработчики приёма-передачи состояние всех регистров, которые могут влиять на состояние интересующего нас регистра.


                                1. 0xd34df00d
                                  20.10.2018 21:59

                                  Это, опять же, на самом деле вопрос формализации семантики языка, ФП тут по большому счёту ни при чём.

                                  Это всё можно расписать в терминах состояния вычислительной машины и small-step evaluation semantics. Например, перемежая каждый шаг вычисления вашей программы с шагом обновления состояния железа. Вы по-прежнему думаете о ваших функциях как явно работающих с внешним состоянием, просто кроме них ещё есть внешняя среда. Аппаратные прерывания там, не знаю.

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


                                  1. Gryphon88
                                    20.10.2018 22:39

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


                                    1. 0xd34df00d
                                      20.10.2018 22:42

                                      Эх.

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

                                      Вон, за что в некоторых кругах джаву (или некоторое её подмножество) любят — про неё просто сели, взяли и доказали, что выполняются progress и preservation теоремы, а это означает, что в ней физически нет UB.


                                      1. Gryphon88
                                        20.10.2018 22:59

                                        Значит, я что-то не понял.

                                        Это всё можно расписать в терминах состояния вычислительной машины и small-step evaluation semantics. Например, перемежая каждый шаг вычисления вашей программы с шагом обновления состояния железа.
                                        Как я это прочитал:
                                        — Заводим список регистров, в прерываниях обновляем состояние
                                        — Вешаем на таймер планировщик через высокоприоритетное прерывание, в котором, допустим, на нечётных тиках прерывания глобально разрешены, а на чётных — запрещены. Когда прерывание запрещены, выполняем квант фоновой программы, когда разрешены — общаемся непосредственно с железом.
                                        Т.е. взяли и просели по производительности минимум вдвое


                                        1. 0xd34df00d
                                          20.10.2018 23:34

                                          Надо различать формализацию семантики языка и её реализацию.


                                          1. Gryphon88
                                            21.10.2018 01:28

                                            Не спорю. Но вот как положить предложенное решение на архитектуру и типовые требования — никак не соображу.


                                            1. 0xd34df00d
                                              21.10.2018 01:49

                                              А как сейчас получается, что аппаратные прерывания ничего не прерывают посреди такта?


                                              1. Gryphon88
                                                21.10.2018 01:52

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


                                                1. 0xd34df00d
                                                  21.10.2018 01:55

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


                                                  1. Gryphon88
                                                    21.10.2018 02:04

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


                                                    1. 0xd34df00d
                                                      21.10.2018 02:09

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


                      1. axe_chita
                        20.10.2018 20:08

                        Либо это функция, которая никогда не возвращает управление (abort, например).

                        Функция abort() вызывает немедленное прекращение программы. Очистка буферов файлов не про­изводится. Функция возвращает вызывающему процессу значение 3 (обычно операционной системе).

                        Основное назначение функции abort() — это предотвращение закрытия файлов некорректно функционирующей программой.

                        Так что abort() это возможность ОС понять что пользовательская программа попала в «сумеречную зону»


                        1. 0xd34df00d
                          20.10.2018 20:39

                          Я знаю, что такое abort, но за справку спасибо.


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


                          1. axe_chita
                            20.10.2018 21:04

                            Функция abort() это ситуация в программе как КФ «Бриллиантовая рука» Шеф, все пропало, всё пропало! Гипс снимают, клиент уезжает! т.е. форс-мажор. И в Си, насколько я помню, исключения (Try catch trow) не завезли?
                            И для ОС главное понимать что процесс неожиданно умер, а там уже другая логика, перезапустить процесс, сообщить куда надо об инциденте, перезагрузится, остановить исполнение.


                    1. axe_chita
                      20.10.2018 19:54

                      И может вернуть код успеха или ошибки, или количество действительно прочитанных/записанных блоков

                      Функция fread() считывает count объектов — каждый объект по size символов в длину — из потока, указанного stream, и помещает их в символьный массив, указанный в buf. Указатель пози­ции в файле продвигается вперед на количество считанных символов.

                      Функция fread() возвращает количество действительно считанных объектов. Если количество считанных объектов меньше, чем это указано при вызове, то либо произошла ошибка, либо был достигнут конец файла. Чтобы определить, что именно имело место, нужно использовать feof() или ferror().


                  1. 0xd34df00d
                    20.10.2018 06:58

                    Естественно функция должна возвращать значение, иначе какая она тогда функция?

                    В том и проблема, что void в С и производных — костыль, а не полноценный тип. Попробуйте объявите значение типа void.


                    1. axe_chita
                      20.10.2018 20:19

                      void — нет значения, пусто. И K&R через #define void int считал что он целое. скорее всего void первоначально был принять для оптимизации кодогенерации (чтоб не тратить время на передачу ненужного результата функции)


                      1. 0xd34df00d
                        20.10.2018 20:41

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

                        void, к которому мы так привыкли по сям, на самом деле населён каким-то единственным значением, которое мы, увы, не можем назвать (и это, может, нормально в С, но это имеет практические последствия в плюсах, в частности, в шаблонном коде).

                        Ну и, в конце концов, посмотрите, как записывается отрицание в интуиционистской логике, например.


                        1. axe_chita
                          20.10.2018 21:38

                          из справочника

                          Тип void имеет три назначения. Первое — указание о невозвращении значения функцией. Второе — указание о неполучении параметров функцией. Третье — создание нетипизированных указателей.

                          Так что вы пошли в какуюто эзотерику, ищите какойто полноценный void?
                          [Сарказм вкл]Давайте тогда искать полноценный int! Ну почему меня ограничивают какими-то рамками?
                          И почему только от -32768 до +32767?
                          Нет от -2147483648 до +2147483647 меня тоже категорически не устраивает, не по фэншую.
                          Вы что издеваетесь? Ваши -9,223,372,036,854,775,808 до 9,223,372,036,854,775,807 это такая мелочь в размерах вселенной.[Сарказм выкл]


                          1. Gryphon88
                            20.10.2018 21:48

                            именно поэтому я предпочитаю uint8_t


                            1. axe_chita
                              20.10.2018 22:39

                              ;) наш человек, а то long, int, unsigned char понимаешь


                          1. 0xd34df00d
                            20.10.2018 22:18

                            Ну почему эзотерика, это всё было обнаружено, обосновано и рассмотрено задолго не то что до С, а даже до Алгола-60. И было бы это эзотерикой, не обсуждался бы сейчас в комитете C++ вопрос regular void.

                            Этот ваш void — это на самом деле unit type (забавно, там даже есть маленькая секция «Void type as unit type» как раз к нашему разговору).

                            Этот мой void — это действительно по-настоящему пустой тип, вроде такого, например.


                            1. axe_chita
                              20.10.2018 23:09

                              Ну во первых void он не мой, а часть стандарта ANSI Си
                              И в вашей ссылке про unit type имеется следующий абзац

                              In C, C++, C#, D and Java, void is used to designate a function that does not return anything useful, or a function that accepts no arguments. The unit type in C is conceptually similar to an empty struct, but a struct without members is not allowed in the C language specification. Instead, 'void' is used in a manner that simulates some, but not all, of the properties of the unit type, as detailed below. Like most imperative languages, C allows functions that do not return a value; these are specified as having the void return type. Such functions are called procedures in other imperative languages like Pascal, where a syntactic distinction, instead of type-system distinction, is made between functions and procedures.

                              По моемому я это уже говорил несколько раз ранее.
                              И мы с вами обсуждаем не Idris который компилируется в набор промежуточных представлений, а из них — в си-код, при исполнении которого используется копирующий сборщик мусора с применением алгоритма Чейни, а язык Си. И кстати C!=C++, так что там плющилы обсуждают, насильников не касается.;)
                              BTW если Си так плох (не имеет истинного void) то почему IDRIS генерирует Си-код?


                              1. 0xd34df00d
                                20.10.2018 23:38

                                По моемому я это уже говорил несколько раз ранее.

                                Ну так я могу в плюсах написать


                                struct Void {};
                                
                                Void f()
                                {
                                    Void v;
                                    return v;
                                }

                                А вот написать


                                void f()
                                {
                                    void v;
                                    return v;
                                }

                                я, увы, не могу (о чём и был мой исходный комментарий).


                                И кстати C!=C++, так что там плющилы обсуждают, насильников не касается.;)

                                Насильников-то, может, и не касается, а вот вполне себе практические последствия оно вполне себе демонстрирует.


                                BTW если Си так плох (не имеет истинного void) то почему IDRIS генерирует Си-код?

                                Потому что С-код генерировать удобно как промежуточное представление, это как LLVM IR, только без завязки на LLVM. Я бы понял такое возражение, если бы С был языком реализации, а не бекенда.


                                Да и это не единственный бекенд. Там в штатной поставке и JS есть, а вообще у идриса есть даже php-бекенд, но это же ни о чём не говорит. Напротив, сам автор того бекенда пишет «Many hosting providers, for example, still support PHP but don't necessarily allow you to write in anything nicer (maybe they'll let you write Node.js, but it's debatable how much nicer that is...)», хехе.


                                1. axe_chita
                                  21.10.2018 06:36

                                  А вот написать

                                  void f()
                                  {
                                  void v;
                                  return v;
                                  }

                                  я, увы, не могу (о чём и был мой исходный комментарий).

                                  А зачем? В вашем коде return как минимум лишний, в заголовке и так описывается что функция не возвращает ничего. В строке void v; вы пропустили * перед v. Ну и в параметрах функции, если вы решили до конца исползовать void, вы забыли указать что ваша функция получает значение void.
                                  Понимаете Си не объектно-ориентированный язык, он появился как ИНСТРУМЕНТАЛЬНЫЙ язык для написания ОС и приложений аж 46 лет назад. И авторы писали его как ИНСТРУМЕНТ, а не как средство описания ВСЕГО. Вообще нет плохих или хороших языков программирования, есть области где нужный язык НАИБОЛЕЕ предпочтителен. В конце концов компилятор переведет вашу программу или в ассемблер, или машкод, и процессору будет глубоко фиолетово как был сформирован этот код, вручную, ассемблером, прямым шитым кодом в Форте, интерпретатором Бейсика, компилятора Си или Паскаля и так далее. Язык программирования в первую очередь средство создания программ, а уж в какой парадигме вы желаете это делать, выбирать вам.


                                  1. 0xd34df00d
                                    21.10.2018 07:41

                                    В вашем коде return как минимум лишний, в заголовке и так описывается что функция не возвращает ничего.

                                    Таких функций очень мало математически.


                                    Но я, в общем, понял, что математика — это ерунда, как оно там сделано в С — куда важнее.


                                    1. axe_chita
                                      21.10.2018 10:54

                                      Таких функций очень мало математически.

                                      exit(),abort(),clrscr() навскидку, и да Си не язык математики.
                                      И вот такой кусок кода (да он страшно корявый, но всеже)
                                      Файл 1
                                      int count;
                                      extern void display(void);
                                      int main(void)
                                      {
                                      count = 10;
                                      display();
                                      return 0;
                                      }

                                      Файл 2
                                      #include <stdio.h>
                                      extern int count;
                                      void display(void)
                                      {
                                      printf("%d", count);
                                      }


                                      И мы язык программирования Си обсуждаем, с его сильными и слабыми сторонами, или математическую абстракцию?
                                      Если вы желаете не ерундовую символьную математику, то посмотрите в сторону Лиспа, по крайней мере в Derive использовался диалект muLisp при обработке компьютерной алгебры.

                                      Использование языка программирования Си часто приводит к написанию очень опасного кода. Но это не совсем справедливое обвинение. Такие проекты, как OpenBSD, показывают, что возможно писать безопасный код на Си. Проблема Си та же, что и в ассемблере — язык открывает вам не только все возможности архитектуры, но и кое-что ещё. Он дает все возможности для написания безопасного кода, но не делает эти вещи сам.

                                      И давайте, не будем холиварить, у каждого языка есть своя ниша, и нет вины Си в том что он такой популярный, может он просто востребованный?
                                      Мира вам!
                                      Кому нравится поп, кому — попадья, а кому — попова дочка


        1. vvzvlad
          19.10.2018 16:16

          А если он одинаковый, то какай смысл подвигать его в качестве учебника? Можно сделать один и тот же код, который будет работать и на 8-битном МК и на Core i8 под виндой, но он же превратится в два кардинально разных бинарника, о каком понимании может идти речь?


          1. Goron_Dekar
            19.10.2018 17:10

            Да хватит брать экзальтированные и редкие платформы типа Core i8.
            Легко сделать один и тот же код, который будет компиляться во что-то очень похожее на AVR и на PIC.


            1. vvzvlad
              19.10.2018 17:19

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


            1. dimaviolinist
              19.10.2018 18:19

              AVR — фон Нейман.
              PIC — Гарвард.
              Там кардинально по разному будет компиляться. Для примера достаточно переключений страниц памяти на PICe.
              И, судя по этому вашему комментарию, вы прекрасно это знаете.
              P.S. Я как раз такой любитель МК, который старается считать такты в прерываниях :)


              1. Sun-ami
                19.10.2018 19:35

                AVR — это тоже Гарвард. Вот ARM — это фон Нейман.


          1. axe_chita
            19.10.2018 19:17

            Понимание в том что программе на Си необязательна операционная система, и она может управлять непосредственно лифтом © K&R
            Бинарники (ассемблеры) разные, а вот логика программы одна, плюс Си мобильный и переносимый язык. К примеру есть такая замечательная книга Р.Берри, Б.Микинз «Язык Си Введение для программистов». Авторы книги предлагают изучить язык Си на примере транслятора RatC и приводят текст самого транслятора. Так вот этот транслятор мог генерировать код как для 8 разрядного процессора intel 8080, так и для 32 разрядного VAX. Более того компилятор мог выполнить самокомпиляцию, т.е. откомпилировать собственный код.
            И в Квейке, между прочим, тоже был Си подобный QuakeC.


            1. vvzvlad
              19.10.2018 22:01

              Это сборище замечательных фактов, спасибо.
              Только все-таки, как си позволяет понять принцип работы компьютера, если поднимает абстракцию от железа до такой степени, что становится переносимым?


              1. Source
                20.10.2018 13:09

                Никак :-)
                Чтобы понять принцип работы компьютера, придётся поизучать хотя бы один ассемблер. На самом деле, это довольно увлекательное занятие.


              1. axe_chita
                20.10.2018 20:25

                Я и не утверждал что Си поможет вам понять принцип работы компьютера. Объяснить принцип работы ОС Си может практически. А что бы понять как работает компьютер (процессор) вам нужно разбираться с ассемблером, ну или в крайнем случае ознакомиться с Forth.

                Керниган говорит: «Си — инструмент, острый, как бритва: с его помощью можно создать и элегантную программу, и кровавое месиво». По выражению Алена Голуба[19], Си и Си++ «… дают вам столько гибкости, что если у вас нет желания и способности призвать себя к порядку, то в итоге вы можете получить гигантский модуль не поддающейся сопровождению тарабарщины, притворяющийся к тому же компьютерной программой. Вы можете поистине делать всё при помощи этих языков, даже если вы этого не хотите».


    1. firedragon
      19.10.2018 20:28

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


  1. Sdima1357
    19.10.2018 12:31
    +4

    Совсем наоборот, изучая С — вы будете вынуждены разобраться как работает компьютер :)


    1. alfaX
      19.10.2018 17:13

      Похоже автор намекал на это.


  1. 3dcryx
    19.10.2018 12:31

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


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


    1. Ogra
      19.10.2018 12:53

      Ну вообще-то да, было уже подробно на Хабре об этом.



    1. Jef239
      19.10.2018 18:11

      Это значит, что есть архитектуры для которых сложно написать эффиктивный компилятор С?

      Да, ПЛК, например. Когда машина аппаратно заточена под ladder. Кстати, ассемблер ПЛК более-менее стандартизован.

      Там одна из проблем — очень неудобно циклы делать. Ну совсем для другого стиля предназначен процессор.


      1. Vanellope
        20.10.2018 05:34

        Совсем не факт, что процессоры ПЛК имеют свою особенную архитектуру. Все эти ладдеры и ассемблеры — программа прикладного уровня, возможно, исполняемая в интерпретаторе в ОС ПЛК, либо преобразуемая в некий байткод. А придуманы они, чтобы прикладной программист не наступал на грабли. Внутри же ПЛК те же АРМы, интелы и даже 8-битные AVR, и крутятся там РТОСы и прочие ОСы, от Линукса до ДОСа. И написаны они наверняка на С.


        1. Jef239
          20.10.2018 21:59
          -1

          В этом смысле и X86 — всего лишь байт-код для интерпретации микропрограммами. А внутри любого современного X86 сидит RISC-процессор с совсем иной архитектурой.

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

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

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

          Дело не в написании кода, дело в эксплуатации. Большая система, 8 тысяч датчиков и 2 тысячи выходов. Как думаете, какой шанс, что все будет работать? Да нулевой. И при поломке счет идет на минуты. Больше 3х часов в месяц потеряно — премия снимается со всей службы. И вот тут, при эксплуатации, ladder выигрывает в десятки раз у Си-подобных языков. Все сигналы видны в графическом виде. И время обхода поломки занимает порядка 2-3 минут.

          То есть за 2-3 минуты меняется код программы так, чтобы обойти сбоящий датчик. Попробуйте повторить это на Си.


          1. Vanellope
            21.10.2018 06:37

            Спасибо за экскурс в историю, но в настоящее время ограничивать возможности ПЛК путем применения различных процессоров никто не будет. А если ПИД регулятор надо добавить, значит DSP дополнительно добавлять будем? Хардварный Ladder это тоже неплохо, можно и на ПЛИС реализовать, но здесь опять же принудительное ограничение функционала, сейчас даже простые программируемые реле умеют не только логику, но и аналоговый ввод/вывод, регуляторы и коммуникационные возможности, причем функционал расширяется сменой прошивки. А выполнять много-много тасков, с различной периодичностью вызова?
            Большое преимущество современных ПЛК — в возможности выбора, какой язык использовать для проекта или даже для отдельного POU. Это позволяет взять LD для простых логических выражений (замена релейных схем же), но для ПИД использовать уже FBD, а для сложной математики или обработки строк — ST. И выстрелить в ногу не получится — в чужую область памяти через указатели не попадешь, стек не переполнишь, неопределенного поведения не будет.
            За минус спасибо, но дело не только в эксплуатации. Программирование это что? Алгоритм, программная реализация и отладка. LD и FBD (а тем более SFC) позволяют легко представить алгоритм в виде программы, что позволяет избежать возможных ошибок на этапе программной реализации, а затем упростить процесс отладки. Поиск отказавшего датчика — тот же процесс отладки, но вот попробуйте разобраться в проблемах сетевого обмена на LD или IL. А еще если надо поддерживать код, написанный неизвестно когда и неизвестно кем. Вот сейчас на работе есть задача разобраться с кодом для старенького TSX, программа вся на ST и абсолютно без символьных таблиц, сплошной
            "IF((%MW9241=%MW11061 AND %MW9249=%MW11069)OR
            (%MW9257=%MW11077 AND %MW9265=%MW11085)OR
            (%MW9273=%MW11093 AND %MW9281=%MW11101))AND
            (%MW18313=%MW9213)AND(%MW18314=%MW9214)AND
            (%MW18315=%MW9215)THEN
            SET %M221;
            ELSE
            RESET %M221;
            END_IF;"
            Вот где настоящая археология по листингам дизассемблера.


            1. Jef239
              21.10.2018 09:21

              Такое впечатление, что вы продаете ПЛК Simatic. От ПЛК прежде всего нужна наработка на отказ порядка 20 лет, а не скорость. Скана в 20-30 мс обычно хватает. Много тасков — очень прилично снижает надежность.

              попробуйте разобраться в проблемах сетевого обмена на LD или IL.
              Я как раз этот кусок и писал. Написал, впятером проверили, код подписали и 15 лет работает.

              в чужую область памяти через указатели не попадешь,
              Почему? На IL/LD/FBD в OMRON — вполне читается и пишется. Более того, можно программно и блокировки ставить. Прямо из IL/LD/FBD.

              программа вся на ST и абсолютно без символьных таблиц, сплошной
              «IF((%MW9241=%MW11061 AND %MW9249=%MW11069)OR
              А это ответ на вопрос, почему ladder лучше ST или Си. Лучше в LD диасссемблируйте, код понятней будет.

              Просто у меня опыт на OMRON, а у вас на Simatic. Вот и смотрит каждый со своей колокольни.


  1. shiru8bit
    19.10.2018 12:38
    -2

    Я часто слышал от людей такое

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


    1. alexxisr
      19.10.2018 13:38

      gcc умеет чистый с.
      K&R + стандарт покрывают 99% знаний о С.
      Но для изучения «как работает компьютер» по-моему лучше ассемблер.


      1. shiru8bit
        19.10.2018 13:46

        Когда я учил C, в районе конца прошлого века, книжек именно по C так и не нашлось, а все доступные компиляторы и литература были для плюсов (BC++3.1, BC++5, BC++B3, MSVC6). Да, конечно, обратная совместимость есть, но при вхождении с нулевым уровнем сходу разобраться, где плюсы, а где нет, и в чём разница, просто невозможно. То есть, если сейчас человек захочет с нуля изучить чистый C, да ещё сидя под Windows или Linux, у него будут определённые затруднения. А плюсы, особенно современные, 'для понимания работы компьютера' уже примерно так же далеки от железа, как любые другие современные же ЯВУ.

        Однозначно, для поставленной задачи лучше подходит ассемблер.


        1. alexxisr
          19.10.2018 14:25

          gcc -std=c99
          (стандарт можно выбрать по вкусу) — компилирует чистый С, ругаясь на всё с++ -ное
          так что проблем с компилятором в наше время нет, даже под виндой
          Я не уверен, но кажется был и досовский ТурбоС, который не С++.
          Керниган с сотоварищем написали свою книгу тоже не в этом тысячелетии — я ее читал как раз в конце 90х.
          Так что было бы желание.


          1. shiru8bit
            19.10.2018 20:57

            Человеку искушённому всё кажется простым и понятным, 'было бы желание'. Но новички ещё не обладают знанием, что им надо искать, как им надо искать, и зачем. Новичок (человек, не знающий, как работает компьютер) с gcc, разбирающийся в стандартах и ключах командной строки — это очень вряд ли. Да что там, знание командной строки для значительного количества современных программистов — это просто чёрная магия какая-то. Самый популярный баг-репорт, который я регулярно получаю — 'ваша программа не работает, показывает чёрное окно и закрывается!'.


      1. dimaviolinist
        19.10.2018 18:24

        Но для изучения «как работает компьютер» по-моему лучше ассемблер.

        Есть ещё варианты.


  1. Ogra
    19.10.2018 12:46

    Ручное управление памятью, ассемблерные вставки и/или интринсики, просмотр скомпилированного ассемблерного когда в отладчике.
    Можно увидеть как компилятор кладет переменную в регистр, и можно предотвратить это с помощью volatile. Можно сравить Array-of-structures и structure-of-arrays, и влияние процессорного кэша на производительность.

    На вопрос «нужно ли учить С для понимания работы компьютера» — ответ однозначен, «да, нужно». Только Си и его инструменты позволяют это сделать. Понятно, что можно просто выучить Си и не понимать, как работает компьютер, но вопрос то сформулирован по-другому. Если вы хотите понять, как работает компьютер — учите Си.


    1. cheshircat
      19.10.2018 13:24
      +2

      Я считаю если цель стоит понять как работает компьютер, то нужно изучить язык Ассемблера.

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

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

      Что касается фразы: «хочешь понять как работает компьютер, изучай C». Я думаю, это уже сродни мифу, который поддерживают люди не писавшие на C/C++. Потому что на С пишут ОС, драйвера и многое другое «низоуровневое» и он представляется как что-то сложное, работающее чуть ли не на уровне CPU. :)


      1. Ogra
        19.10.2018 13:47
        +1

        Потому что на С пишут ОС, драйвера и многое другое

        Ассемблер необходим для понимания, как работает процессор. Если к этому добавить ОС и драйвера (а это уже уровень Си), то и получится понимание работы компьютера.
        Си — нужен, но не достаточен, асм тоже надо.


      1. LynXzp
        19.10.2018 13:56

        Для тех кому тоже интересно: тут считают количество инструкций (1-3.5k), тут хороший список.

        P.S. думаю что ассемблера не достаточно
        … чтобы понять как работает процессор (потому что кеш, конвейер, предвыборка, ME,...). Интересно и просто можно маленькую часть из этих вещей узнать в книге Криса Касперски «Техника оптимизации программ. Эффективное использование памяти». (просто вспомнилось, хорошее чтиво, но явно не учебник о том «как работает компьютер»)


        1. cheshircat
          19.10.2018 14:13

          Ассемблера 80186 вполне достаточно для образовательных целей. А там Вряд ли в наше время стоит писать программы на ассемблере для PC.

          … чтобы понять как работает процессор

          Дальше можно поизучать как перемещаются электроны и т.п. В теме стоит вопрос изучить как работает компьютер, я думаю стоит остановиться на этом уровне абстракции :) По этому поводу мне очень нравится ответ Ричарда Феймана про магниты


          1. LynXzp
            19.10.2018 15:17

            Ну и правильно Фейнман говорит — смотря кому отвечать (или зачем ему нужны эти знания), если нужно написать драйвер для USB сигнализации то даже C не обязательно, лишь бы было совместимо с требуемой ОС. Если оптимизируется кодек то ассемблера уже маловато. А если познание ради познания то и устройство транзисторов с квантовой физикой будут тоже подходящими. Первые два примера — классический формат хабра, последний — гиктаймса. Нельзя просто так взять и остановиться. Всегда кому-то захочется to go deeper. В принципе получается что «нужно учить С для понимания работы компьютера» в первом примере что я привел. В других примерах это выражение будет ложным.


  1. zeronice
    19.10.2018 13:01
    +1

    Как минимум вы поймете, что компьютер работает на так, как вы думали.


  1. BiosUefi
    19.10.2018 14:06

    А что тогда ближе? ООП, лямбда-исчисления, или функциональное программирование?


  1. PerlPower
    19.10.2018 14:20

    .


  1. sami777
    19.10.2018 14:24

    1. Нет никакого смысла собирать код на Си в ассемблер. Сразу создается двоичный код. В своем первом компиляторе для этого было меню «Disassembly», что предполагало возможность создания ассемблерного листинга из двоичного кода.
    2. Byte в яве строго типизирован и имеет размер тип unssigned. Просто char предполагает signed, т. е. сравнение в явном виде недопустимо.
    3. Если вы считаете, что изучение Си позволит вам понять как работает компьютер, то я скажу, что изучение ассемблера 86 позволит вам стать богом в копьютерном понимание. Что значит, что Си всегда есть и будет языком высокого уровня и никакого понимания, как работает компьютер он вам не даст!.. Ну разве что вы не работает на каком нибудь питоне или другом «безобразно высоком» языке программирования.


    1. Ogra
      19.10.2018 14:35

      Нет никакого смысла собирать код на Си в ассемблер.

      Про clang и llvm слышали?


      1. sami777
        19.10.2018 21:30

        Уверен на 100%, что шланг при компоновке двоичного кода из Си исходника не нуждается в компиляции Си кода в ассемблер! И что создание ассемблерного листинга это не более, чем плюшка и по сути к созданию бинарного кода никакого отношения не имеет!
        Но если вы считаете иначе, то мне будет очень интересно почитать из какого нибудь серьезного источника, что шланг обязательно пере собирает код под ассемблер, чтобы в последствии скомпоновать исполнительный файл.


    1. 0xd34df00d
      19.10.2018 16:35

      Просто char предполагает signed

      Нет, просто char ничего не предполагает, поэтому технически в C три типа: char, signed char, unsigned char.


      1. axe_chita
        21.10.2018 06:53

        Таки не правы насчет char

        Тип Длина в битах Диапазон
        char 8 от-128 до 127
        unsigned char 8 от 0 до 255
        signed char 8 от-128 до 127
        int 16 от-32768 до 32767
        unsigned int 16 от 0 до 65535
        signed int 16 от -32768 до 32767
        short int 16 от -32768 до 32767
        unsigned short int 16 от 0 до 65535
        signed short int 16 от -32768 до 32767
        long int 32 от -2147483648 до 2147483647
        unsigned long int 32 от 0 до 4294967295
        signed long int. 32 от -2147483648 до 2147483647

        Использование signed для целочисленных типов является избыточным (но допустимым), поскольку объявление целочисленных типов по умолчанию предполагает знаковое число.


        1. 0xd34df00d
          21.10.2018 07:53

          Ваш источник некорректен (а кстати, какой он)? Я вот взял драфт стандарта С тута.


          char 8 от-128 до 127

          Процитирую 5.2.4.2.1/2:


          If the value of an object of type char is treated as a signed integer when used in an
          expression, the value of CHAR_MIN shall be the same as that of SCHAR_MIN and the
          value of CHAR_MAX shall be the same as that of SCHAR_MAX. Otherwise, the value of
          CHAR_MIN shall be 0 and the value of CHAR_MAX shall be the same as that of
          UCHAR_MAX.

          Более того, из /1 следует, что остальная часть вашей цитаты тоже, вообще говоря, не совсем корректна.


          Далее, 6.2.5/4:


          There are five standard signed integer types, designated as signed char, short
          int, int, long int, and long long int.

          Обратите внимание, что у signed char единственного в списке явно указан signed.


          6.2.5/14 вместе с /15 окончательно закрывают вопрос:


          The type char, the signed and unsigned integer types, and the floating types are
          collectively called the basic types. Even if the implementation defines two or more basic
          types to have the same representation, they are nevertheless different types.

          и соответственно


          The three types char, signed char, and unsigned char are collectively called
          the character types. The implementation shall define char to have the same range35,
          representation, and behavior as either signed char or unsigned char.

          Сноска 35 закрывает вопрос ну совсем окончательно:


          CHAR_MIN, defined in <limits.h>, will have one of the values 0 or SCHAR_MIN, and this can be
          used to distinguish the two options. Irrespective of the choice made, char is a separate type from the
          other two and is not compatible with either.

          Такие дела.


  1. finlandcoder
    19.10.2018 14:30

    Си учить не нужно. Можно просто на нём покодить на предмете «Архитектура ЭВМ». Посмотреть на сколько хватит рекурсии. Сколько можно выделить памяти и упасть. Какой максимальный фаил можно прочитать. Как работает стек и хип. Когда изучают разницу между x32 и x64.


  1. semibiotic
    19.10.2018 14:45

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

    Си, с учетом UB-догм, а тем более с оптимизацией, переходящей черты здравого смысла, в разы сложнее.


    1. maxzhurkin
      20.10.2018 22:09

      Существование UB — всего лишь отражение того факта, сто синтаксическая сторона C мощнее семантической — было бы странно и неудобно, если бы было наоборот, или если бы были и места в семантике, которые невозможно выразить в тексте, и текст, который не имеет конкретного семантического выражения (собственно, суть UB)


  1. martin_wanderer
    19.10.2018 15:06

    Сам вопрос какой-то странный: чтобы понимать работу компьютера надо надо таки понимать, как он устроен. Это, например, рассказывают в вузовском курсе «Архитектура микропроцессорных систем.

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


  1. Wilk
    19.10.2018 16:07

    Здравствуйте!

    Я чаще слышу высказывание «чтобы понять, как работает процессор/компьютер/микроконтроллер нужно изучить ассемблер». И я не вполне согласен с данным высказыванием.

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

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


    1. Gryphon88
      19.10.2018 16:38

      Изучение низкоуровневых вещей идёт двумя путями:
      — Целенаправленно стреляем себе в ногу
      — Пытаемся «сделать хорошо», но на ногу всё равно смотрим.
      Ассемблер гарантирует, что дырка в ноге появится сразу и в предсказуемом месте. В плюсах дырок может быть десяток и появятся они в течение месяца.
      Выше уже давали список инструкций х86, теоретически, можно залезть почти куда угодно, кроме предсказателя ветвлений. Для последнего (а точнее, его обмана) пишут книги по оптимизации программ.


  1. saipr
    19.10.2018 16:26

    Вообще классическое и исчерпывающее описание языка C на четырех страницах было дано Эндрю Таненбаумом в приложении к учебному пособию «Operating Systems: Design and Implementation» (1987, ISBN 0-13-637406-9). Там же на паре страницах был описан и ассемблер. Это классика.


  1. k1p1sh
    19.10.2018 18:46

    Вот хотел оценки постам поставить, а оказывается не могу, но могу писать!
    — Касаемо темы: Язык Си не позволяет понять принципы работы компьютера! Си позволяет понять работу Операционной системы и не более, в нем (как и указано в статье) есть очень тонкие моменты по части распределения памяти и при этом отсутствуют любые регуляторы, которые в более высоких языках (читать компиляторах и интерпритаторах) присутствуют, для контроля утечек и переполнений.
    — Из выше сказанного: Обучать чайника языку СИ ну просто не логично! это язык профессионалов, уже знакомых с понятием «утечка» и «переполнение»! Для чайников есть множество сред, в которых они точно также поймут всю суть «работы компьютера» — Камень в огород Сишников — А вы знаете как работает компьютер? (Вот я знаю! знаю не один десяток процов.)


    1. axe_chita
      19.10.2018 19:29

      Вы Forth-еров спросите, они вам в польской нотации, с использованием стека, и правила «если определение не влазит на один экран, то его надо резать на более мелкие части», они вам объяснят как работает компьютер.

      Йоды магистра речи тайна раскрыта, оказывается, на форте программист старый есть он просто.


    1. Sdima1357
      19.10.2018 19:37

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

      Знаю десяток

      А я вот начинал с фортрана /алгола на БЭСМ 6 и пишу до сих пор, от встроенных до GPU, и не могу сказать что в совершенстве знаю архитектуры процессоров, все время приходится курить мануалы.


  1. unxed
    20.10.2018 01:08

    При наличии таких инструментов как rust, начинать знакомство с программированием и устройством компьютера с инструмента, созданного, чтобы стрелять себе в ногу, потому что ничего лучше на момент его создания изобрести не успели?

    Ну, хм.

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


    1. 0xd34df00d
      20.10.2018 07:00

      При наличии таких формализмов, как лямбда-исчисление и построенные поверх него системы типов, начинать знакомство с программированием…


    1. maxzhurkin
      20.10.2018 22:17

      А вы транспортом пользуетесь?
      Это же ярчайший пример того, как не надо делать (езда на транспорте невозможна без того, чтобы полагаться на внимательность людей, как свою, так и других)!