Как стать автором
Обновить

Комментарии 109

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

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

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

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

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

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

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

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

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

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

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

Функция, которая что-нибудь делает. Начиная от работы с железом и заканчивая изменением данных по указателю.
НЛО прилетело и опубликовало эту надпись здесь
Глобальное состояние в тех же микроконтроллерах банально есть, хранить неизменяемую копию неудобно и бессмысленно, тут делать вид, что глобального состояния нет, себе дороже. Как можно передать состояние, если оно может быть изменено по внешнему или аппаратному событию событию? Проще явно или неявно держать его в голове.
НЛО прилетело и опубликовало эту надпись здесь
Честно говоря, не видел ФП на микроконтроллерах. Ниже «пионерский» код отправки байта через аппаратный UART на AVR. Тут мы читаем 2 регистра состояния (глобальное состояние) и пишем в регистр без кода возврата (грязная функция).
void SendByte(char byte)
{
while(!(UCSRA & (1<<UDRE))); /*Ждём опустошения регистра данных*/
UDR=byte; /*Кладём байт в регистр данных*/
}
Как тут можно уйти от глобального состояния и сайд-эффектов? Я не троллю, но правда не понимаю, как мешать IO с ФП.
НЛО прилетело и опубликовало эту надпись здесь
Насколько я понимаю, работа с сетью на компьютере должна отличаться от работа с вводом-выводом на МК. Сетевая карта, получив пакет, дёргает прерывание и в нём перекладывает пакет в собственный буфер, а чаще в память, откуда пользовательская программа данные и берёт. В МК предотвращение «застревания» данных (а иногда и сброс флагов прерываний) лежит на программисте. Т.е. мы имеем ситуацию, когда ресурс может модифицироваться как программно (из main loop или обработчика прерывания), так и чисто аппаратно (из внешнего мира или при вызове другого прерывания, если вложенные прерывания запрещены). Нет никакой гарантии, что в IO smth мы вообще попадём. Тут вопрос, как не тащить в обработчики приёма-передачи состояние всех регистров, которые могут влиять на состояние интересующего нас регистра.
НЛО прилетело и опубликовало эту надпись здесь
Можно, но на глазок — очень дорого. И я не совсем понимаю, зачем, во многих случаях дешевле подольше посидеть с отладчиком, чем брать камень, который сможет одновременно тянуть основную задачу и вести самопроверку.
НЛО прилетело и опубликовало эту надпись здесь
Значит, я что-то не понял.
Это всё можно расписать в терминах состояния вычислительной машины и small-step evaluation semantics. Например, перемежая каждый шаг вычисления вашей программы с шагом обновления состояния железа.
Как я это прочитал:
— Заводим список регистров, в прерываниях обновляем состояние
— Вешаем на таймер планировщик через высокоприоритетное прерывание, в котором, допустим, на нечётных тиках прерывания глобально разрешены, а на чётных — запрещены. Когда прерывание запрещены, выполняем квант фоновой программы, когда разрешены — общаемся непосредственно с железом.
Т.е. взяли и просели по производительности минимум вдвое
НЛО прилетело и опубликовало эту надпись здесь
Не спорю. Но вот как положить предложенное решение на архитектуру и типовые требования — никак не соображу.
НЛО прилетело и опубликовало эту надпись здесь
Вход в прерывание блокирует все прочие. Когда прерывание обработано, начинают обрабатываться случившиеся во время обработки первого, в соответствие с таблицей приоритетов или живой очереди.
НЛО прилетело и опубликовало эту надпись здесь
Извините, не пояснил: пусть у нас два аппаратных таймера, у каждого зарегистрирован обработчик переполнения. Пока обрабатывается переполнение одного, второй будет продолжать тикать, инкрементируя значение в соответствующем регистре. Периферия независима от ядра и друг от друга.
НЛО прилетело и опубликовало эту надпись здесь
Либо это функция, которая никогда не возвращает управление (abort, например).

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

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

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

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

НЛО прилетело и опубликовало эту надпись здесь
void — нет значения, пусто. И K&R через #define void int считал что он целое. скорее всего void первоначально был принять для оптимизации кодогенерации (чтоб не тратить время на передачу ненужного результата функции)
НЛО прилетело и опубликовало эту надпись здесь
из справочника
Тип void имеет три назначения. Первое — указание о невозвращении значения функцией. Второе — указание о неполучении параметров функцией. Третье — создание нетипизированных указателей.

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Такое впечатление, что вы продаете ПЛК 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. Вот и смотрит каждый со своей колокольни.
Я часто слышал от людей такое

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

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

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

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

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

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

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

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

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

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

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

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

Про clang и llvm слышали?
Уверен на 100%, что шланг при компоновке двоичного кода из Си исходника не нуждается в компиляции Си кода в ассемблер! И что создание ассемблерного листинга это не более, чем плюшка и по сути к созданию бинарного кода никакого отношения не имеет!
Но если вы считаете иначе, то мне будет очень интересно почитать из какого нибудь серьезного источника, что шланг обязательно пере собирает код под ассемблер, чтобы в последствии скомпоновать исполнительный файл.
НЛО прилетело и опубликовало эту надпись здесь
Таки не правы насчет 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 для целочисленных типов является избыточным (но допустимым), поскольку объявление целочисленных типов по умолчанию предполагает знаковое число.
НЛО прилетело и опубликовало эту надпись здесь
Ваш источник некорректен (а кстати, какой он)? Я вот взял драфт стандарта С тута.

Мои источники Си_(язык_программирования)
а точнее Си_(язык_программирования)#Целые_числа
и 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 рассматривается как целое без знака.
А все остальное набросанное вами в качестве «доказательств» не более чем дым и вода, и забалтывание.
НЛО прилетело и опубликовало эту надпись здесь
Стандарт сам про это говорит.

Стандарт разъясняет вам что 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 ++.
НЛО прилетело и опубликовало эту надпись здесь
Чем это отличается от моего исходного тезиса, который был «Нет, просто 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;
}
НЛО прилетело и опубликовало эту надпись здесь
Мне он нравится, но он не имеет отношения. Чёрным по белому написано, что 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;
}


?

Ну пожалуйста…
НЛО прилетело и опубликовало эту надпись здесь
Зачем? Я не устану цитировать наиболее релевантный кусок стандарта, явно говорящий о том, что 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?
НЛО прилетело и опубликовало эту надпись здесь
Си учить не нужно. Можно просто на нём покодить на предмете «Архитектура ЭВМ». Посмотреть на сколько хватит рекурсии. Сколько можно выделить памяти и упасть. Какой максимальный фаил можно прочитать. Как работает стек и хип. Когда изучают разницу между x32 и x64.
Если человек, действительно, хочет понимать как работает компьютер, ему нужно познакомиться с языком ассемблера (хотя бы одним).

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

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

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

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

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

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

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

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

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

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

Ну, хм.

Реально, язык, для которого пришлось придумывать концепцию статических анализаторов, чтобы хоть как-то обойти изначальные дефекты архитектуры. Просто идеальная модель для обучения тому, как не надо делать (полагаться на внимательность людей, например).
НЛО прилетело и опубликовало эту надпись здесь
А вы транспортом пользуетесь?
Это же ярчайший пример того, как не надо делать (езда на транспорте невозможна без того, чтобы полагаться на внимательность людей, как свою, так и других)!
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории