Нужно ли учить C для понимания работы компьютера?

https://words.steveklabnik.com/should-you-learn-c-to-learn-how-the-computer-works
  • Перевод
Я часто слышал, что для понимания работы компьютера люди предлагают изучать 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 по своей сути не предназначен для изучения компьютера, но это хороший вариант.

В программировании так много всего, чему стоит поучиться. Желаю вам успехов на этом пути.
Поделиться публикацией
Комментарии 109
    0
    Очень нехватает в этой статье конкретезации того, о каком компьютере идёт речь.
    Если о тех, которых большинство, то С самое близкое к тому, как они работают. Ну нет у большинства arm'ов и Pic'ов ни векторизации, ни предиктора.
      +2
      Смотря что значит «понимать как работает компьютер», если совсем понимать, то начинать стоит с ассемблера.
        0
        Ассемблер у всех компьютеров разный. С один.
          +1
          C тоже бывает разный. Разные стандарты, особенно до C89, разные ограничения, разные библиотеки. На одной платформе нет stdlib вообще, на другой нет динамического выделения памяти, на третьей нет float, разные размеры переменных, на древностях нет const, enum, и так далее.

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

          int foo(x)
            int x;
            { int y;
                 y = x * x;
                 return y;
            }
            +2
            Или так:
            foo(x,z)
              char x;
              { int y;
                   y = x * z;
                   return y;
              }
              –1
              Не покатит код, компилятор выдаст ошибку необъявленная переменная Z, плюс вывесит предупреждение необходимо приведение типов
                +2
                Ну я же наверное знал что писал, не просто так выдумал? (Обратное тоже возможно, но как-то маловероятно на хабре) Есть такое правило «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 аналогично, даже не смотря на то что его вообще не было когда такой синтаксис использовался.
                  –2
                  Естественно функция должна возвращать значение, иначе какая она тогда функция? возвращаемый тип void и был добавлен стандарт ANSI C и во многих компиляторах вышедших незадолго до его принятия делали такую великолепную заглушку #define void int чтобы сделать текст программы более совместимым.
                  На на счет использования необъявленной переменной, и без ошибки при компиляции… Ну это какойто BASIC, причем до 90-х годов, если не до 80-х. Жуть и дичь.
                    +2
                    > Какая же она функция без возвращения значения?
                    Нормальная функция, мы же не в рамках паскаля говорим с его процедурами.

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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

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


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


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

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

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

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

                                    Тут, кстати, нет вообще никакой разницы, аппаратное прерывание поменяло это самое глобальное состояние, или же настолько же внешний для вас драйвер сетевой карты пополам с ОС.
                                      0
                                      Можно, но на глазок — очень дорого. И я не совсем понимаю, зачем, во многих случаях дешевле подольше посидеть с отладчиком, чем брать камень, который сможет одновременно тянуть основную задачу и вести самопроверку.
                                        +1
                                        Эх.

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

                                        Вон, за что в некоторых кругах джаву (или некоторое её подмножество) любят — про неё просто сели, взяли и доказали, что выполняются progress и preservation теоремы, а это означает, что в ней физически нет UB.
                                          0
                                          Значит, я что-то не понял.
                                          Это всё можно расписать в терминах состояния вычислительной машины и small-step evaluation semantics. Например, перемежая каждый шаг вычисления вашей программы с шагом обновления состояния железа.
                                          Как я это прочитал:
                                          — Заводим список регистров, в прерываниях обновляем состояние
                                          — Вешаем на таймер планировщик через высокоприоритетное прерывание, в котором, допустим, на нечётных тиках прерывания глобально разрешены, а на чётных — запрещены. Когда прерывание запрещены, выполняем квант фоновой программы, когда разрешены — общаемся непосредственно с железом.
                                          Т.е. взяли и просели по производительности минимум вдвое
                                            –1
                                            Надо различать формализацию семантики языка и её реализацию.
                                              +1
                                              Не спорю. Но вот как положить предложенное решение на архитектуру и типовые требования — никак не соображу.
                                                –1
                                                А как сейчас получается, что аппаратные прерывания ничего не прерывают посреди такта?
                                                  +1
                                                  Вход в прерывание блокирует все прочие. Когда прерывание обработано, начинают обрабатываться случившиеся во время обработки первого, в соответствие с таблицей приоритетов или живой очереди.
                                                    –1
                                                    Ну вот я не вижу проблем с таким подходом. Не надо разрешать или запрещать прерывания на каждом чётном-нечётном такте, они разрешены как обычно, просто если прерывания не случилось, то вы считаете, что соответствующий шаг SSES ничего не поменял.
                                                      +1
                                                      Извините, не пояснил: пусть у нас два аппаратных таймера, у каждого зарегистрирован обработчик переполнения. Пока обрабатывается переполнение одного, второй будет продолжать тикать, инкрементируя значение в соответствующем регистре. Периферия независима от ядра и друг от друга.
                                                        –1
                                                        С формализацией конкурентности порядком геморроя и без всяких микроконтроллеров и прочих аппаратных вещей, это совсем другой разговор.
                            –1
                            Либо это функция, которая никогда не возвращает управление (abort, например).

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

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

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

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


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

                                0
                                Функция abort() это ситуация в программе как КФ «Бриллиантовая рука» Шеф, все пропало, всё пропало! Гипс снимают, клиент уезжает! т.е. форс-мажор. И в Си, насколько я помню, исключения (Try catch trow) не завезли?
                                И для ОС главное понимать что процесс неожиданно умер, а там уже другая логика, перезапустить процесс, сообщить куда надо об инциденте, перезагрузится, остановить исполнение.
                            0
                            И может вернуть код успеха или ошибки, или количество действительно прочитанных/записанных блоков
                            Функция fread() считывает count объектов — каждый объект по size символов в длину — из потока, указанного stream, и помещает их в символьный массив, указанный в buf. Указатель пози­ции в файле продвигается вперед на количество считанных символов.

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

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

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

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

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

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

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

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

                                      Этот мой void — это действительно по-настоящему пустой тип, вроде такого, например.
                                        0
                                        Ну во первых 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 генерирует Си-код?
                                          0
                                          По моемому я это уже говорил несколько раз ранее.

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


                                          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...)», хехе.

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

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

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

                                            А зачем? В вашем коде return как минимум лишний, в заголовке и так описывается что функция не возвращает ничего. В строке void v; вы пропустили * перед v. Ну и в параметрах функции, если вы решили до конца исползовать void, вы забыли указать что ваша функция получает значение void.
                                            Понимаете Си не объектно-ориентированный язык, он появился как ИНСТРУМЕНТАЛЬНЫЙ язык для написания ОС и приложений аж 46 лет назад. И авторы писали его как ИНСТРУМЕНТ, а не как средство описания ВСЕГО. Вообще нет плохих или хороших языков программирования, есть области где нужный язык НАИБОЛЕЕ предпочтителен. В конце концов компилятор переведет вашу программу или в ассемблер, или машкод, и процессору будет глубоко фиолетово как был сформирован этот код, вручную, ассемблером, прямым шитым кодом в Форте, интерпретатором Бейсика, компилятора Си или Паскаля и так далее. Язык программирования в первую очередь средство создания программ, а уж в какой парадигме вы желаете это делать, выбирать вам.
                    0
                    А если он одинаковый, то какай смысл подвигать его в качестве учебника? Можно сделать один и тот же код, который будет работать и на 8-битном МК и на Core i8 под виндой, но он же превратится в два кардинально разных бинарника, о каком понимании может идти речь?
                      0
                      Да хватит брать экзальтированные и редкие платформы типа Core i8.
                      Легко сделать один и тот же код, который будет компиляться во что-то очень похожее на AVR и на PIC.
                        0
                        Вообще я имел ввиду i5, опечатался. Не суть важно, хоть пентиум. Все равно различия какого-нибудь ARM и x86 таковы, что один и тот же код на си будет компилироваться в очень разный ассемблер.
                          0
                          AVR — фон Нейман.
                          PIC — Гарвард.
                          Там кардинально по разному будет компиляться. Для примера достаточно переключений страниц памяти на PICe.
                          И, судя по этому вашему комментарию, вы прекрасно это знаете.
                          P.S. Я как раз такой любитель МК, который старается считать такты в прерываниях :)
                            +1
                            AVR — это тоже Гарвард. Вот ARM — это фон Нейман.
                          0
                          Понимание в том что программе на Си необязательна операционная система, и она может управлять непосредственно лифтом © K&R
                          Бинарники (ассемблеры) разные, а вот логика программы одна, плюс Си мобильный и переносимый язык. К примеру есть такая замечательная книга Р.Берри, Б.Микинз «Язык Си Введение для программистов». Авторы книги предлагают изучить язык Си на примере транслятора RatC и приводят текст самого транслятора. Так вот этот транслятор мог генерировать код как для 8 разрядного процессора intel 8080, так и для 32 разрядного VAX. Более того компилятор мог выполнить самокомпиляцию, т.е. откомпилировать собственный код.
                          И в Квейке, между прочим, тоже был Си подобный QuakeC.
                            0
                            Это сборище замечательных фактов, спасибо.
                            Только все-таки, как си позволяет понять принцип работы компьютера, если поднимает абстракцию от железа до такой степени, что становится переносимым?
                              0

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

                                0
                                Я и не утверждал что Си поможет вам понять принцип работы компьютера. Объяснить принцип работы ОС Си может практически. А что бы понять как работает компьютер (процессор) вам нужно разбираться с ассемблером, ну или в крайнем случае ознакомиться с Forth.
                                Керниган говорит: «Си — инструмент, острый, как бритва: с его помощью можно создать и элегантную программу, и кровавое месиво». По выражению Алена Голуба[19], Си и Си++ «… дают вам столько гибкости, что если у вас нет желания и способности призвать себя к порядку, то в итоге вы можете получить гигантский модуль не поддающейся сопровождению тарабарщины, притворяющийся к тому же компьютерной программой. Вы можете поистине делать всё при помощи этих языков, даже если вы этого не хотите».
                        0
                        C позволяет понять как работать с памятью. Чего автор по умыслу либо лукавству не озвучил.
                        Собственно понимание как работает память, это и есть тот рубеж который отделяет джуна от миддла.
                        +13
                        Совсем наоборот, изучая С — вы будете вынуждены разобраться как работает компьютер :)
                          0
                          Похоже автор намекал на это.
                          –1

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


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

                            0
                            Ну вообще-то да, было уже подробно на Хабре об этом.
                              0
                                –1
                                Это значит, что есть архитектуры для которых сложно написать эффиктивный компилятор С?

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

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

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

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

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

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

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

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

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

                                      Спасибо за экскурс в историю, но в настоящее время ограничивать возможности ПЛК путем применения различных процессоров никто не будет. А если ПИД регулятор надо добавить, значит 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
                                        Такое впечатление, что вы продаете ПЛК 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. Вот и смотрит каждый со своей колокольни.
                                –4
                                Я часто слышал от людей такое

                                Никогда не слышал от людей такого. Вот совсем. И учитывая, что плюсы не упоминаются, а компиляторов чистого C и книг по нему уже очень давно так просто не найти, это какая-то окаменелость особого периода, когда такая постановка вопроса могла быть актуальна.
                                  0
                                  gcc умеет чистый с.
                                  K&R + стандарт покрывают 99% знаний о С.
                                  Но для изучения «как работает компьютер» по-моему лучше ассемблер.
                                    0
                                    Когда я учил C, в районе конца прошлого века, книжек именно по C так и не нашлось, а все доступные компиляторы и литература были для плюсов (BC++3.1, BC++5, BC++B3, MSVC6). Да, конечно, обратная совместимость есть, но при вхождении с нулевым уровнем сходу разобраться, где плюсы, а где нет, и в чём разница, просто невозможно. То есть, если сейчас человек захочет с нуля изучить чистый C, да ещё сидя под Windows или Linux, у него будут определённые затруднения. А плюсы, особенно современные, 'для понимания работы компьютера' уже примерно так же далеки от железа, как любые другие современные же ЯВУ.

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

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

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

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

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

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

                                        Ассемблер необходим для понимания, как работает процессор. Если к этому добавить ОС и драйвера (а это уже уровень Си), то и получится понимание работы компьютера.
                                        Си — нужен, но не достаточен, асм тоже надо.
                                          0
                                          Для тех кому тоже интересно: тут считают количество инструкций (1-3.5k), тут хороший список.
                                          P.S. думаю что ассемблера не достаточно
                                          … чтобы понять как работает процессор (потому что кеш, конвейер, предвыборка, ME,...). Интересно и просто можно маленькую часть из этих вещей узнать в книге Криса Касперски «Техника оптимизации программ. Эффективное использование памяти». (просто вспомнилось, хорошее чтиво, но явно не учебник о том «как работает компьютер»)
                                            +1
                                            Ассемблера 80186 вполне достаточно для образовательных целей. А там Вряд ли в наше время стоит писать программы на ассемблере для PC.

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

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

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

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

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

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

                                                    0
                                                    Таки не правы насчет 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 для целочисленных типов является избыточным (но допустимым), поскольку объявление целочисленных типов по умолчанию предполагает знаковое число.
                                                      0

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


                                                      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.

                                                      Такие дела.

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

                                                        Мои источники Си_(язык_программирования)
                                                        а точнее Си_(язык_программирования)#Целые_числа
                                                        и Limits.h
                                                        Которые ссылаются именно на WG14 N1124 (англ.). ISO/IEC 9899 — Programming languages — C — Approved standards. ISO/IEC JTC1/SC22/WG14 (6 мая 2005). — Стандарт ISO/IEC 9899:1999 (C99) + ISO/IEC 9899:1999 Cor. 1:2001(E) (TC1 — Technical Corrigendum 1 от 2001 года) + ISO/IEC 9899:1999 Cor. 2:2004(E) (TC2 — Technical Corrigendum 2 от 2004 года).
                                                        Но давайте прочитаем о чем нам говорит стандарт
                                                        5.2.4.2.1 Sizes of integer types <limits.h>
                                                        1 The values given below shall be replaced by constant expressions suitable for use in #if
                                                        preprocessing directives. Moreover, except for CHAR_BIT and MB_LEN_MAX, the
                                                        following shall be replaced by expressions that have the same type as would an
                                                        expression that is an object of the corresponding type converted according to the integer
                                                        promotions. Their implementation-defined values shall be equal or greater in magnitude
                                                        (absolute value) to those shown, with the same sign.
                                                        — number of bits for smallest object that is not a bit-field (byte)
                                                        CHAR_BIT 8
                                                        — minimum value for an object of type signed char
                                                        SCHAR_MIN -127 // −(2^7 − 1)
                                                        — maximum value for an object of type signed char
                                                        SCHAR_MAX +127 // 2^7 − 1
                                                        — maximum value for an object of type unsigned char
                                                        UCHAR_MAX 255 // 2^8 − 1
                                                        — minimum value for an object of type char
                                                        CHAR_MIN see below
                                                        — maximum value for an object of type char
                                                        CHAR_MAX see below

                                                        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. 15) The value UCHAR_MAX shall equal 2^CHAR_BIT − 1.

                                                        перевод примечания
                                                        Если значение объекта типа char рассматривается как целое число со знаком при использовании в выражении, значение CHAR_MIN должно быть таким же, как значение SCHAR_MIN, а значение CHAR_MAX должно быть таким же, как значение SCHAR_MAX. В противном случае значение CHAR_MIN должно быть 0, а значение CHAR_MAX должно быть таким же, как значение UCHAR_MAX. 15) Значение UCHAR_MAX должно быть равно 2 ^ CHAR_BIT — 1.

                                                        Параграф 4 (Цитировать надо полностью и корректно)
                                                        There are five standard signed integer types, designated as signed char, short
                                                        int, int, long int, and long long int. (These and other types may be
                                                        designated in several additional ways, as described in 6.7.2.) There may also be
                                                        implementation-defined extended signed integer types.
                                                        28) The standard and extended signed integer types are collectively called signed integer types. 29)

                                                        перевод
                                                        Существует пять стандартных целочисленных типов с знаком, обозначенных как знаковый char, short int, int, long int и long long int. (Эти и другие типы могут быть назначены несколькими дополнительными способами, как описано в 6.7.2.) Могут также существовать расширенные типы целочисленных подписей, определенные реализацией.28) Стандартные и расширенные со знаком целочисленные типы являются коллективно называемыми целыми типами со знаком.29)
                                                        Обратите внимание что упоминание (со знаком) относится ко всем целочисленным типам

                                                        5 параграф (для понимания что char имеют одинаковый размер)
                                                        An object declared as type signed char occupies the same amount of storage as a ‘‘plain’’ char object. A ‘‘plain’’ int object has the natural size suggested by the architecture of the execution environment (large enough to contain any value in the range INT_MIN to INT_MAX as defined in the header <limits.h>)
                                                        перевод
                                                        Объект, объявленный как тип char со знаком, занимает тот же объем памяти, что и объект ''обычный'' char. Объект ''обычный'' int имеет естественный размер, предложенный архитектурой среды выполнения (достаточно большой, чтобы содержать любое значение в диапазоне INT_MIN до INT_MAX, как определено в заголовке <limits.h>)

                                                        6 параграф (в котором почему то целочисленные типы со знаком являются прародителями для целочисленных типов без знака, а не наоборот)
                                                        For each of the signed integer types, there is a corresponding (but different) unsigned integer type (designated with the keyword unsigned) that uses the same amount of storage (including sign information) and has the same alignment requirements. The type _Bool and the unsigned integer types that correspond to the standard signed integer
                                                        types are the standard unsigned integer types. The unsigned integer types that correspond to the extended signed integer types are the extended unsigned integer types. The standard and extended unsigned integer types are collectively called unsigned integer types. 30)

                                                        перевод
                                                        Для каждого из целочисленных типов со знаком существует соответствующий (но другой) целочисленный тип без знака(обозначенный ключевым словом unsigned), который использует тот же объем хранения (включая информацию о знаке) и имеет те же требования к выравниванию. Тип _Bool и целые типы без знака, которые соответствуют стандартным знаковым целым числам типы являются стандартными целыми типами без знака. Без знаковые целочисленные типы, которые соответствуют расширенным целым типам со знаком, являются расширенными целыми типами без знака.Стандартный и расширенный целые типы без знака совместно называются целыми без знака. 30)

                                                        Параграф 14 (в котором дается объяснение что в Си есть только ТРИ (3) базовых типа char, integer, float)
                                                        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.
                                                        перевод
                                                        Тип char, целые типы с знаком и без знака и типы с плавающей запятой совместно называются базовыми типами. Даже если реализация определяет два или более основных типа, имеющих одинаковое представление, они, тем не менее, разные типы.

                                                        Параграф 15 (в котором от разработчиков реализации требуют что бы char был реализован полностью, с диапазоном, представлением, и поведением)
                                                        The three types char, signed char, and unsigned char are collectively called the character types. The implementation shall define char to have the same range, representation, and behavior as either signed char or unsigned char.
                                                        перевод
                                                        Три типа char, signed char и unsigned char совместно называются типами символов. Реализация должна определять char для того, чтобы иметь тот же диапазон, представление и поведение в виде либо char со знаком, либо char без знака.

                                                        Параграф 17 (Чтоже он такой сиротинушка, в котором char числе прочих относят к целочисленным типам)
                                                        The type char, the signed and unsigned integer types, and the enumerated types are collectively called integer types. The integer and real floating types are collectively called
                                                        real types.

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

                                                        сноска 35 (в которой нам объясняют что минимальное значение CHAR_MIN может быть равно нулю ИЛИ SCHAR_MIN описанной в limits.h)
                                                        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.
                                                        перевод
                                                        CHAR_MIN, определенный в <limits.h>, будет иметь одно из значений 0 или SCHAR_MIN, и это можно использовать для различения двух параметров. Независимо от выбранного выбора, char является отдельным типом от двух других и не совместим ни с одним из них.

                                                        Conclusion так сказать
                                                        Основываясь на Пункте 5.2.4.2.1 Sizes of integer types <limits.h> мы делаем следующий вывод:
                                                        Если char используется в выражениях таких как ch1+ch2, ch1-ch2, ch1*ch2, ch1/ch2 он должен рассматриваться как целое со знаком.
                                                        В случае логических операций ch1<ch2, ch1>ch2, ch1==ch2, ch1!=ch2 тип char рассматривается как целое без знака.
                                                        А все остальное набросанное вами в качестве «доказательств» не более чем дым и вода, и забалтывание.
                                                          0
                                                          А все остальное набросанное вами в качестве «доказательств» не более чем дым и вода, и забалтывание.

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


                                                          Независимо от выбранного выбора, char является отдельным типом от двух других и не совместим ни с одним из них.

                                                          Стандарт сам про это говорит.


                                                          Ну и да, я понимаю, что проблемы сиплюсплюсников Ъ сишников не волнуют, но всё же раз, два, три.


                                                          ЧСХ последний пример в сишечке тоже немножко ломается, а -pedantic-errors сделает это ошибкой, а не ворнингом.

                                                            0
                                                            Стандарт сам про это говорит.

                                                            Стандарт разъясняет вам что char что со знаком, что без знака является ОТДЕЛЬНЫМ типом и не совместим ни с целочисленным (со знаком/без знака) типом, ни с типом с плавающей запятой.
                                                            Чтобы вам было проще принять это процитирую еще раз параграф 14 раздела 6.2.5 Типы:
                                                            Параграф 14 (в котором дается объяснение что в Си есть только ТРИ (3) базовых типа char, integer, float)
                                                            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.
                                                            перевод
                                                            Тип char, целые типы с знаком и без знака и типы с плавающей запятой совместно называются базовыми типами. Даже если реализация определяет два или более основных типа, имеющих одинаковое представление, они, тем не менее, разные типы.


                                                            Ну и да, я понимаю, что проблемы сиплюсплюсников Ъ сишников не волнуют, но всё же раз, два, три.

                                                            Со своим уставом в чужой монастырь не ходят.
                                                            Понимаете Си и Си плюс плюс, это при всей схожести два РАЗНЫХ языка. И плюсы это не Си с классами и не Си с объектами. Да первый компилятор C++ был выполнен на препроцессоре С, но это было очень давно.
                                                            И указатели это «опасная бритва» Си, можно побриться, а можно и горло перерезать.
                                                            И вообще
                                                            Синтаксис C++ унаследован от языка C. Одним из принципов разработки было сохранение совместимости с C. Тем не менее, C++ не является в строгом смысле надмножеством C; множество программ, которые могут одинаково успешно транслироваться как компиляторами C, так и компиляторами C++, довольно велико, но не включает все возможные программы на C.
                                                            С++
                                                            Выбор именно C в качестве базы для создания нового языка программирования объясняется тем, что язык C:

                                                            является многоцелевым, лаконичным и относительно низкоуровневым языком;
                                                            подходит для решения большинства системных задач;
                                                            исполняется везде и на всём;
                                                            стыкуется со средой программирования UNIX.
                                                            — Б. Страуструп. Язык программирования C++. Раздел 1.6[14]

                                                            Несмотря на ряд известных недостатков языка C, Страуструп пошёл на его использование в качестве основы, так как «в C есть свои проблемы, но их имел бы и разработанный с нуля язык, а проблемы C нам известны». Кроме того, это позволило быстро получить прототип компилятора (cfront[en]), который лишь выполнял трансляцию добавленных синтаксических элементов в оригинальный язык C.

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

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


                                                            C++ не включает в себя C[править | править код]
                                                            Несмотря на то, что большая часть кода C будет справедлива и для C++, C++ не является надмножеством C и не включает его в себя. Существует и такой верный для C код, который неверен для C++. Это отличает его от Objective C, ещё одного усовершенствования C для ООП, как раз являющегося надмножеством C.

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

                                                            Более того, код, верный для обоих языков, может давать разные результаты в зависимости от того, компилятором какого языка он оттранслирован. Например, на большинстве платформ следующая программа печатает «С», если компилируется компилятором C, и «C++» — если компилятором C++. Так происходит из-за того, что символьные константы в C (например, 'a') имеют тип int, а в C++ — тип char, а размеры этих типов обычно различаются.

                                                            #include <stdio.h>

                                                            int main()
                                                            {
                                                            printf("%s\n", (sizeof('a') == sizeof(char))? «C++»: «C»);
                                                            return 0;
                                                            }

                                                            C++_не_включает_в_себя_C

                                                            А чтобы ловить все подозрительные или не переносимые места программе на Си нужно использовать LINT (статический анализатор). Причем использовать его постоянно.
                                                            Ну или обратить внимание на MISRA_C стандарт разработки программного обеспечения на языке Си, разработанный MISRA (Motor Industry Software Reliability Association). Цель стандарта — улучшить безопасность, переносимость и надежность программ для встраиваемых систем. Также существует набор похожих руководящих принципов для языка C++ под названием MISRA C ++.
                                                              0
                                                              Стандарт разъясняет вам что char что со знаком, что без знака является ОТДЕЛЬНЫМ типом и не совместим ни с целочисленным (со знаком/без знака) типом, ни с типом с плавающей запятой.

                                                              Чем это отличается от моего исходного тезиса, который был «Нет, просто char ничего не предполагает, поэтому технически в C три типа: char, signed char, unsigned char.»? Какая, если конкретнее, его часть неверна?


                                                              Понимаете Си и Си плюс плюс, это при всей схожести два РАЗНЫХ языка.

                                                              Поэтому я и привёл пример на чистом С в самом конце, который вы, видимо, проигнорировали.


                                                              А чтобы ловить все подозрительные или не переносимые места программе на Си нужно использовать LINT (статический анализатор).

                                                              Из чего не следует, что все подозрительные или непереносимые места в программе на Си могут быть отловлены только линтером.

                                                                0
                                                                Чем это отличается от моего исходного тезиса, который был «Нет, просто char ничего не предполагает, поэтому технически в C три типа: char, signed char, unsigned char.»? Какая, если конкретнее, его часть неверна?

                                                                Скажите вам чем не нравиться параграф 14 в котором черным по белому устанавливается три (3) базовых типа char, int, float в Си? А signed/unsigned модификаторы базового типа. И применение модификатора не изменяет тип.

                                                                Поэтому я и привёл пример на чистом С в самом конце, который вы, видимо, проигнорировали.

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

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

                                                                Более того, код, верный для обоих языков, может давать разные результаты в зависимости от того, компилятором какого языка он оттранслирован. Например, на большинстве платформ следующая программа печатает «С», если компилируется компилятором C, и «C++» — если компилятором C++. Так происходит из-за того, что символьные константы в C (например, 'a') имеют тип int, а в C++ — тип char, а размеры этих типов обычно различаются.


                                                                #include <stdio.h>

                                                                int main()
                                                                {
                                                                printf("%s\n", (sizeof('a') == sizeof(char))? «C++»: «C»);
                                                                return 0;
                                                                }


                                                                так какие результаты выдает программа для Си и С++?
                                                                #include <stdio.h>

                                                                int main()
                                                                {
                                                                printf("%s\n", (sizeof('a') == sizeof(char))? «C++»: «C»);
                                                                return 0;
                                                                }
                                                                  0
                                                                  Скажите вам чем не нравиться параграф 14 в котором черным по белому устанавливается три (3) базовых типа char, int, float в Си?

                                                                  Мне он нравится, но он не имеет отношения. Чёрным по белому написано, что char отличен и от signed char, и от unsigned char.


                                                                  То есть, нет, вы, конечно, можете утверждать, что signed char ~ unsigned char, но это уже перебор.


                                                                  Или таки не перебор? Ведь вы дальше пишете, что


                                                                  И применение модификатора не изменяет тип.

                                                                  То есть, signed int — то же самое, что unsigned int?


                                                                  Всё чудесатее и чудесатее.


                                                                  И указатели это «опасная бритва» Си, можно побриться, а можно и горло перерезать.

                                                                  То есть, разработчики компилятора порезались? Или как этот аргумент мне интерпретировать?


                                                                  так какие результаты выдает программа для Си и С++?

                                                                  Разные, очевидно. А в C98 ещё были разрешены VLA (теперь они optional), а в C++ их не было никогда, тоже несовместимость. А в C у структуры может быть поле с именем class, а в сях — нет. Можно ещё про это всё повспоминать.

                                                                    0
                                                                    Мне он нравится, но он не имеет отношения. Чёрным по белому написано, что char отличен и от signed char, и от unsigned char.

                                                                    Тогда пожалуйста укажите в какой месте The type char, the signed and unsigned integer types, and the floating types are collectively called the basic types. указано более трех типов?

                                                                    То есть, signed int — то же самое, что unsigned int?

                                                                    int остается int-ом при применением модификатора signed/unsigned а не превращает его в float или char. Так понятнее?
                                                                    Так же как и применение других модификатором в языке (их тоже привести?)

                                                                    То есть, разработчики компилятора порезались? Или как этот аргумент мне интерпретировать?

                                                                    Указатели потенциально опасный инструмент, который может привести к непредсказуемому поведению программы. В случае исполнения такой программы в среде без защиты памяти может рухнуть среда исполнения. В случае среды исполнения с защитой памяти будет остановлена только сама программа.
                                                                    Вы наверное не помните, но в начале 90-х на BBS по тематическим эхам ходили KnockOut.c исходники без каких либо ошибок, но выбивавшие компилятор. Среди компиляторов по количеству вылетов лидировал Microsoft C, но Борланд и Ватком тоже имели проблемы хотя и намного меньше. Плюс иногда оптимизатор выражений в компиляторе выкидывал такие коленца, что не спасали никакие скобки.

                                                                    Разные, очевидно. А в C98 ещё были разрешены VLA (теперь они optional), а в C++ их не было никогда, тоже несовместимость. А в C у структуры может быть поле с именем class, а в сях — нет. Можно ещё про это всё повспоминать.

                                                                    Ну про Variable Length Arrays вы тоже ошибаетесь (или заблуждаетесь) Variable Length Arrays in C and C++
                                                                    Variable length arrays is a feature where we can allocate an auto array (on stack) of variable size. C supports variable sized arrays from C99 standard. For example, the below program compiles and runs fine in C.

                                                                    Also note that in C99 or C11 standards, there is feature called “flexible array members”, which works same as the above.

                                                                    void fun(int n)
                                                                    {
                                                                    int arr[n];
                                                                    //…
                                                                    }
                                                                    int main()
                                                                    {
                                                                    fun(6);
                                                                    }

                                                                    But C++ standard (till C++11) doesn’t support variable sized arrays. The C++11 standard mentions array size as a constant-expression See (See 8.3.4 on page 179 of N3337). So the above program may not be a valid C++ program. The program may work in GCC compiler, because GCC compiler provides an extension to support them.

                                                                    As a side note, the latest C++14 (See 8.3.4 on page 184 of N3690) mentions array size as a simple expression (not constant-expression).

                                                                    Variable Length Arrays in C and C++
                                                                    Так что фича с массивом переменной длины теперь есть и в Си и в Си++
                                                                    Причем прописана в стандартах, а не опционально.

                                                                    Ну про ключевое слово Си++ class, это такая милая семейная шутка. А еще нельзя использовать alignas friend alignof decltype reinterpret_cast try delete inline typeid typename dynamic_cast mutable
                                                                    catch namespace static_assert new static_cast virtual
                                                                    char16_t explicit noexcept char32_t export nullptr volatile
                                                                    class operator template wchar_t private this constexpr protected thread_local const_cast public throw, я ничего не перепутал?

                                                                    А про совместимость есть статья в википедии Compatibility of C and C++

                                                                    И все таки Георгий Игоревич, что у вас выдает эта программа
                                                                    #include <stdio.h>

                                                                    int main()
                                                                    {
                                                                    printf("%s\n", (sizeof('a') == sizeof(char))? «C++»: «C»);
                                                                    return 0;
                                                                    }


                                                                    ?

                                                                    Ну пожалуйста…
                                                                      0
                                                                      Тогда пожалуйста укажите в какой месте The type char, the signed and unsigned integer types, and the floating types are collectively called the basic types. указано более трех типов?

                                                                      Зачем? Я не устану цитировать наиболее релевантный кусок стандарта, явно говорящий о том, что char не является ни signed char, ни unsigned char: «Irrespective of the choice made, char is a separate type from the other two and is not compatible with either.»


                                                                      О чём мы тут дальше говорим-то?


                                                                      int остается int-ом при применением модификатора signed/unsigned а не превращает его в float или char. Так понятнее?
                                                                      int остается int-ом

                                                                      То есть, unsigned int остаётся int'ом?


                                                                      Надеюсь, нам обоим очевидно, что это довольно бредовое утверждение, и в стандарте есть места, прямо указывающие на обратное? Ну, как пример, поведение unsigned int и [signed] int при overflow?


                                                                      а не превращает его в float или char

                                                                      Из этого не следует, что unsigned int ~ int, что signed char ~ char ~ unsigned char, и из этого не следует.


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

                                                                      Я не очень понимаю, к чему это пространное рассуждение в ответ на пример, явно показывающий, что указатель на char не конвертируется ни в указатель на signed char, ни в указатель на unsigned char. Даже в С.


                                                                      Прям защита Чубаки какая-то. Указатели опасные, поэтому то, что они не конвертируются друг в друга, не означает, что типы несовместимы. Ясно.


                                                                      Так что фича с массивом переменной длины теперь есть и в Си и в Си++
                                                                      Причем прописана в стандартах, а не опционально.

                                                                      Перестаньте читать левые сайты и начните читать соответствующие стандарты, пожалуйста.


                                                                      Во-первых, n1548, драфт C11, 6.7.6.2/4: «Variable length
                                                                      arrays are a conditional feature that implementations need not support;»
                                                                      Так что да, они опциональны в С11.


                                                                      Во-вторых,


                                                                      The C++11 standard mentions array size as a constant-expression See (See 8.3.4 on page 179 of N3337). So the above program may not be a valid C++ program.

                                                                      Нету их в C++11, даже ваша цитата так говорит.


                                                                      В C++14 всё интереснее. В драфте C++14 какая-то прикрученная сбоку костылями ерунда в виде runtime-bound arrays (сделайте Ctrl+F по "runtime bound" в тексте драфта, с ними вообще ничего ж делать нельзя!). В частности, это лишь подмножество VLA: не поддерживается sizeof, нельзя создать многомерный VLA, нельзя сделать typedef, нельзя объявить указатель на него.


                                                                      Доступа к недрафту C++14 у меня нет (релизные версии стандартов, увы, платные), и у меня есть впечатление, что из финальной версии это недоразумение выпилили. Попробую найти доступ чуть позже сегодня, либо, конечно, вы можете потратить 60 баксов и купить финальную версию стандарта сами, потом вместе почитаем.


                                                                      И, кстати, в любом случае, их нет в C++17 (который и является последней версией языка, а не как в той вашей статьей с каких-то geeksforgeeks). C++17 draft, 11.3.4/1,


                                                                      In a declaration T D where D has the form
                                                                      D1 [ constant-expressionopt ] attribute-specifier-seqopt

                                                                      constant-expression. Усё.

                                                                        0
                                                                        Зачем? Я не устану цитировать наиболее релевантный кусок стандарта, явно говорящий о том, что char не является ни signed char, ни unsigned char: «Irrespective of the choice made, char is a separate type from the other two and is not compatible with either.»


                                                                        Затем чтобы читать и усваивать стандарт Си в общем, а не его избранные части.
                                                                        14. The type char (1), the signed and unsigned integer types (2), and the floating types (3) are collectively called the basic types (всего 3 (три) базовых типа)
                                                                        15 The three types char, signed char, and unsigned char are collectively called the character types. The implementation shall define char to have the same range, 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. Здесь прямо подтверждается что char отдельный тип от двух оставшихся базовых типов, и не совместим ни с одним из них.
                                                                        16 An enumeration comprises a set of named integer constant values. Each distinct enumeration constitutes a different enumerated type.
                                                                        17 The type char, the signed and unsigned integer types, and the enumerated types are collectively called integer types. The integer and real floating types are collectively called real types
                                                                        Здесь подтверждается что char, целые со знаком и без знака, тип перечисление совместно называются целыми типами.

                                                                        И стандарт на то и СТАНДАРТ чтобы быть целиком релевантным.

                                                                        То есть, unsigned int остаётся int'ом?

                                                                        Надеюсь, нам обоим очевидно, что это довольно бредовое утверждение, и в стандарте есть места, прямо указывающие на обратное? Ну, как пример, поведение unsigned int и [signed] int при overflow?

                                                                        Я следую стандарту а он прямо утверждает следующее
                                                                        The range of nonnegative values of a signed integer type is a subrange of the corresponding unsigned integer type, and the representation of the same value in each type is the same.31) A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type.
                                                                        Таким образом действия с целыми без знака не может вызвать переполнения.

                                                                        И еще, чтобы не возвращаться к вопросу является ли char в Си целочисленным, выдержка из стандарта страница 14
                                                                        EXAMPLE 2 In executing the fragment
                                                                        char c1, c2;
                                                                        /*… */
                                                                        c1 = c1 + c2;

                                                                        the ‘‘integer promotions’’ require that the abstract machine promote the value of each variable to int size and then add the two ints and truncate the sum. Provided the addition of two chars can be done without overflow, or with overflow wrapping silently to produce the correct result, the actual execution need only produce the same result, possibly omitting the promotions

                                                                        Таким образом char в Си целое.

                                                                        Я не очень понимаю, к чему это пространное рассуждение в ответ на пример, явно показывающий, что указатель на char не конвертируется ни в указатель на signed char, ни в указатель на unsigned char. Даже в С.

                                                                        Прям защита Чубаки какая-то. Указатели опасные, поэтому то, что они не конвертируются друг в друга, не означает, что типы несовместимы. Ясно.


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

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

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

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

                                                                        Вторая причина — правило языка Си, согласно которому все параметры функций должны передаваться по значению. Когда вам нужен эквивалент VAR-параметра языка Паскаль или inout- параметра языка Ada, единственное решение состоит в том, чтобы передать указатель. Этим во многом объясняется плохая читаемость программ на языке Си.

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

                                                                        Статья с одного малоизвестного сайта История языков программирования: 100% «чистый» Си, без единого «плюса»
                                                                        И совсем чуть чуть с попсово-желтушного ресурса ru.wikipedia.org/wiki/Си_(язык_программирования)#Проблемы_и_критика

                                                                        Далее я поскипаю ваши песни про VLA в плюсах (сначала добавили в стандарт, потом дропнули, что стандарт финальный платный и тд и тп)
                                                                        Задам только риторический вопрос (а может мысль вслух): Так что получается Си более прогрессивней Си++ в свете поддержки Variable length array?

                                                                        P.S. Си удерживает второе место (не плохо для не идеального языка с 45 летней историей) с рейтингом 15,376% с ростом в 7% к 2017https://www.tiobe.com/tiobe-index/
                                                                        where idris?
                                                                          0
                                                                          Здесь подтверждается что char, целые со знаком и без знака, тип перечисление совместно называются целыми типами.

                                                                          И что из этого следует?


                                                                          Давайте, дабы не растекаться мыслию по древу, более тезисно, да/нет на три вопроса:


                                                                          char совместим с signed char?
                                                                          char совместим с unsigned char?
                                                                          signed char совместим с unsigned char?


                                                                          Где совместимость определяется как, ну, слово compatible в стандарте.


                                                                          Мои ответы: нет, нет, нет.


                                                                          Пока что не надо приводить цитат, ни из стандарта, ни с википедии, ни с викиучебников. Давайте сначала определимся, о чём мы спорим.


                                                                          Таким образом действия с целыми без знака не может вызвать переполнения.

                                                                          Ага, отлично. А у signed int какое поведение при переполнении?


                                                                          И еще, чтобы не возвращаться к вопросу является ли char в Си целочисленным, выдержка из стандарта страница 14

                                                                          Я чего-то не припомню, чтобы такой вопрос поднимался. Ну, разве что, вы его подняли.


                                                                          Проблемы с указателями

                                                                          А вы, я смотрю, полны упорства и упрямо идёте к цели!


                                                                          Я всё ещё не понимаю, как это доказывает, что тот сишный пример не доказывает несовместимость упомянутых типов.


                                                                          Далее я поскипаю ваши песни про VLA в плюсах

                                                                          Ну ещё бы, они ведь опровергают вашу мысль.


                                                                          сначала добавили в стандарт

                                                                          В драфт стандарта. В черновик.


                                                                          А предложения добавить VLA в той или иной форме в C++ периодически возникают. Ну или раньше возникали, по крайней мере.


                                                                          потом дропнули

                                                                          Таки нашли VLA в C++17? :)


                                                                          стандарт финальный платный

                                                                          От бесплатной версии не откажусь. Не завалялась у вас случайно?


                                                                          Так что получается Си более прогрессивней Си++ в свете поддержки Variable length array?

                                                                          Именно так. В С она есть (пусть и опциональная в C11), в плюсах нет вообще.


                                                                          В плюсах, правда, есть всякие там вектора, поэтому VLA особо не нужны, но это дело такое. Куда важнее, что VLA в стиле C не работают со значимой частью плюсов (в чём можно убедиться, погрепав драфт C++14 по словам «runtime bound» и посмотрев, что с ними можно делать, а что — нельзя), поэтому их добавление создаёт больше проблем, чем решает.


                                                                          where idris?

                                                                          Там же, где и Agda, Coq и Liquid Haskell. А вот Coq, например, используется в Frama-C, если это вам о чём-то говорит, но то такое.


                                                                          Кстати, в куда более распространённых OCaml или Haskell с Void примерно такое же отношение. Но они, увы, неконсистентны как логики, поэтому там всё чуть более скучно.

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

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

                                                      +1
                                                      Существование UB — всего лишь отражение того факта, сто синтаксическая сторона C мощнее семантической — было бы странно и неудобно, если бы было наоборот, или если бы были и места в семантике, которые невозможно выразить в тексте, и текст, который не имеет конкретного семантического выражения (собственно, суть UB)
                                                      0
                                                      Сам вопрос какой-то странный: чтобы понимать работу компьютера надо надо таки понимать, как он устроен. Это, например, рассказывают в вузовском курсе «Архитектура микропроцессорных систем.

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

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

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

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

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

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

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

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

                                                                Ну, хм.

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

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

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

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

                                                                  Самое читаемое