Предисловие


Я несколько раз в своих комментариях ссылался на книгу Эндрю Таненбаума «Operating Systems Design and Implementation» на ее первое издание и на то, как в ней представлен язык Си. И эти комментарии всегда вызывали интерес. Я решил, что пришло время опубликовать перевод этого введения в язык Си. Оно по-прежнему актуально. Хотя наверняка найдутся и те, кто не слышал о языке программировании PL/1, а может даже и об операционной системе Minix.

Это описание интересно также и с исторической точки зрения и для понимания того, как далеко ушел язык Си с момента своего рождения и IT-отрасль в целом.

Хочу сразу оговориться, что мой второй язык французский:

image

Но это компенсируется 46-летним программистским стажем.
Итак, приступим, наступила очередь Эндрю Таненбаума.

Введение в язык Си (стр. 350 — 362)




Язык программирования Cи был создан Деннисом Ритчи из AT&T Bell Laboratories как язык программирования высокого уровня для разработки операционной системы UNIX. В настоящее время язык широко используется в различных областях. C особенно популярен у системных программистов, потому что позволяет писать программы просто и кратко.

Основной книгой, описывающая язык Cи, является книга Брайана Кернигана и Денниса Ритчи « Язык программирования Cи» (1978). Книги по языку Си писали Bolon (1986), Gehani (1984), Hancock and Krieger (1986), Harbison и Steele (1984) и многие другие.

В этом приложении мы попытаемся дать достаточно полное введение в Cи, так что те кто знаком с языками высокого уровня, такими как Pascal, PL/1 или Modula 2, смогут понять большую часть кода MINIX, приведенного в этой книге. Особенности Cи, которые не используются в MINIX, здесь не обсуждаются. Многочисленные тонкие моменты опущены. Акцент делается на чтении программ на Си, а не на написании кода.

А.1. Основы языка Си


Программа на Cи состоит из набора процедур (часто называемых функциями, даже если они не возвращают значений). Эти процедуры содержат объявления, операторы и другие элементы, которые вместе говорят компьютеру что надо делать. На рисунке A-1 показана небольшая процедура, в которой объявляются три целочисленные переменные и присваиваются им значения. Имя процедуры — main (главная). Процедура не имеет формальных параметров, на что указывает отсутствие каких-либо идентификаторов между скобками за именем процедуры. Тело процедуры заключено в фигурные скобки ( { } ). Этот пример показывает, что Cи имеет переменные, и что эти переменные должны быть объявлены до использования. Cи также имеет операторы, в этом примере это операторы присваивания. Все операторы должны заканчиваться точкой с запятой (в отличие от Паскаля, который использует двоеточия между операторами, а не после них).

Комментарии начинаются с символов « / *» и заканчивается символами «* /» и могут занимать несколько строк.

main ()      /* это комментарий */
{
     int i, j, k; 	         /* объявление 3 целочисленных переменных */
     i  =  10; 	        /* присвоить i значение 10 (десятичное число) */
     j  =  i + 015; 	/* присвоить j значение  i + 015 (восьмеричное число) */
     k = j * j + 0xFF;   /* установить k в j * j + 0xFF (шестнадцатеричное число) */
}
Рис. A-l. Пример процедуры в Си.

Процедура содержит три константы. Константа 10 в первом присваивании
это обычная десятичная константа. Константа 015 является восьмеричной константой
(равно 13 в десятичной системе счисления). Восьмеричные константы всегда начинаются с начального нуля. Константа 0xFF является шестнадцатеричной константой (равной 255 десятичной). Шестнадцатеричный константы всегда начинаются с 0x. Все три типа используются в Cи.

А.2. Основные типы данных


Cи имеет два основных типа данных (переменных): целое и символ, объявляемые как int и char, соответственно. Нет отдельной булевой переменной. В качестве булевой переменной используется переменная int. Если эта переменная содержит 0, то это означает ложь/false, а любое другое значение означает истина/true. Cи также имеет и типы с плавающей точкой, но MINIX не использует их.

К типу int можно применять «прилагательные» short, long или unsigned, которые определяют (зависящий от компилятора) диапазон значений. Большинство процессоров 8088 используют 16-битные целые числа для int и short int и 32-битные целые числа для long int. Целые числа без знака (unsigned int) на процессоре 8088 имеют диапазон от 0 до 65535, а не от -32768 до +32767, как это у обычных целых чисел (int). Символ занимает 8 бит.

Спецификатор register также допускается как для int, так и для char, и является подсказкой для компилятору, что объявленную переменную стоит поместить в регистр, чтобы программа работала быстрее.

Некоторые объявления показаны на рис. А — 2.

int i; 			         /* одно целое число */
short int z1, z2; 	        / *два коротких целых числа */
char c; 			/* один символ */
unsigned short int k; 	/* одно короткое целое без знака */
long flag_poll;	        /* 'int' может быть опущено */
register int r; 		/* переменная регистра */

Рис. А-2. Некоторые объявления.

Преобразование между типами разрешено. Например, оператор

flag_pole = i;

разрешен, даже если i имеет тип int, а flag_pole — long. Во многих случаях
необходимо или полезно принудительно проводить преобразования между типами данных. Для принудительного преобразования достаточно поставить целевой тип в скобках перед выражением для преобразования. Например:

р ( (long) i);

предписывает преобразовать целое число i в long перед передачей его в качестве параметра в процедуру p, которая ожидает именно параметр long.

При преобразовании между типами следует обратить внимание на знак.
При преобразовании символа в целое число некоторые компиляторы обрабатывают символы как знаковые, то есть от — 128 до +127, тогда как другие рассматривают их как
без знака, то есть от 0 до 255. В MINIX часто встречаются такие выражения, как

i = c & 0377;

которые преобразует с (символ) в целое число, а затем выполняет логическое И
(амперсанд) с восьмеричной константой 0377. В результате получается, что старшие 8 бит
устанавливаются в ноль, фактически заставляя рассматривать c как 8-битное число без знака, в диапазоне от 0 до 255.

А.3. Составные типы и указатели


В этом разделе мы рассмотрим четыре способа построения более сложных типов данных: массивы, структуры, объединения и указатели (arrays, structures, unions, and pointers). Массив — это коллекция/множество элементов одного типа. Все массивы в Cи начинаются с элемента 0.

Объявление

int a [10];

объявляет массив a с 10 целыми числами, которые будут хранится в элементах массива от [0] до a [9]. Второе, массивы могут быть трех и более измерений, но они не используются в MINIX.
Структура — это набор переменных, обычно разных типов. Структура в Cи похож на record в Паскале. Оператор

struct {int i; char c;} s;

объявляет s как структуру, содержащую два члена, целое число i и символ c.

Чтобы присвоить члену i структуры s значение 6, нужно записать следующее выражение:

s.i = 6;

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

union {int i; char c;} u;

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

Указатели используются для хранения машинных адресов в Cи. Они используются очень и очень часто. Символ звездочка (*) используется для обозначения указателя в объявлениях. Объявление

int i, *pi, a [10], *b[10], **ppi;

объявляет целое число i, указатель на целое число pi, массив a из 10 элементов, массив b из 10 указателей на целые числа и указатель на указатель ppi на целое число.

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

На рисунке A-3 показано объявление массива z структур struct table, каждая из которых имеет
три члена, целое число i, указатель cp на символ и символ с.

struct table {	/* каждая структура имеет тип таблицы */
      int i; 		/ *целое число */
      char *cp, c; 	/* указатель на символ и символ */
} z [20]; 		/* это массив из 20 структур */

Рис. А - 3. Массив структур.

Массивы структур распространены в MINIX. Далее, имя table можно объявить как структуру struct table, которую можно использовать в последующих объявлениях. Например,

register struct table *p;

объявляет p указателем на структуру struct table и предлагает сохранить ее
в register. Во время выполнения программы p может указывать, например, на z [4] или
на любой другой элемент в z, все 20 элементов которой являются структурами типа struct table.

Чтобы сделать p указателем на z [4], достаточно написать

p = &z[4];

где амперсанд в качестве унарного (монадического) оператора означает «взять адрес того, что за ним следует ». Скопировать в целочисленную переменную n значение члена i
структуры, на которую указывает указатель р, можно следующим образом:

n = p->i;

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

n = z [4] .i;

Разница в том, что z [4] является структурой, и оператор точки выбирает элементы
из составных типов (структуры, массивы) напрямую. С помощью указателей мы не выбираем участника напрямую. Указатель предписывает сначала выбрать структуру и только потом выбрать члена этой структуры.

Иногда удобно дать имя составному типу. Например:

typedef unsigned short int unshort;

определяет unshort как unsigned short (короткое целое число без знака). Теперь unshort может быть использован в программе как основной тип. Например,

unshort ul, *u2, u3[5];

объявляет короткое целое число без знака, указатель на короткое целое число без знака и
массив коротких целых без знака.

А.4. Операторы


Процедуры в Cи содержат объявления и операторы. Мы уже видели объявления, так что теперь мы будем рассматривать операторы. Назначение условного оператора и операторов цикла по существу такие же, как и в других языках. Рисунок А – 4 показывает несколько примеров из них. Единственное, на что стоит обратить внимание, это то, что фигурные скобки используются для группировки операторов, а оператор while имеет две формы, вторая из которых похожа на оператор repeat Паскаля.

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

for (<инициализация>; <условие>; <выражение>) оператор;

Тоже самое можно выразить через опертор while:

<инициализация>
while(<условие>) {
	<оператор>;
	<выражение>
} 

В качестве примера рассмотрим следующий оператор:

for (i=0; i <n; i = i+l) a[i]=0;

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

if (x < 0) k = 3;        /* простое оператор if */

if (x > y) {	              /* составной оператор if */
     i = 2;
     k = j + l,
}

if (x + 2 <y) {          /* оператор if-else */
      j  = 2;
      k = j - 1;
} else {
      m = 0;
}

while (n > 0) {	      /* оператор while */
     k = k + k;
     n = n - l;
}

do {	         / * другой вид оператора while */
      k = k + k;
       n = n - 1;
} while (n >  0);

Рис. A-4. Примеры операторов if и while в Cи.

Си имеет также оператор аналогичный case-оператору в языке Pascal. Это switch-оператор. Пример представлен на рисунке А-5. В зависимости от значения выражения, указанного в switch, выбирается тот или иной оператор cаse.

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

Если выражение не связано ни с одним оператором case и оператор default отсутствует, то выполнение продолжается со следующего оператора после оператора switch.

Следует отметить, что для выхода из блока case следует использовать оператор break. Если оператор break отсутствует, то будет выполняться следующий блок case.

switch (k) {
      case 10:
            i = 6;
            break;  /* не выполнять case 20, т.е. завершить выполнение опертора switch */
      case 20:
             i = 2;
             k = 4;
             break;	/ * не выполнять default* /
      default:
            j = 5;
}

Рис. A-5. Пример оператора switch

Оператор break также действует внутри циклов for и while. При этом надо помнить, что если оператор break находится внутри серии вложенных циклов, выход осуществляется только на один уровень вверх.

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

Cи имеет процедуры, которые могут вызываться с параметрами или без параметров.
Согласно Кернигану и Ричи (стр. 121), не разрешено передавать массивы,
структуры или процедуры в качестве параметров, хотя передача указателей на все это
допускается. Есть ли книга или нет ее (так и всплывет в памяти:- «Если жизнь на Марсе, нет ли жизни на Марсе»), многие компиляторы языка Си допускают структуры в качестве параметров.
Имя массива, если оно написано без индекса, означает указатель на массив, что упрощает передачу указателя массива. Таким образом, если a является именем массива любого типа, его можно передать в процедуру g, написав

g(а);

Это правило действует только для массивов, на структуры это правило не расапространяется.
Процедуры могут возвращать значения, выполняя оператор return. Этот оператор может содержать выражение, результат выполнения которого будет возвращено в качестве значения процедуры, но вызвавшая процедура может смело игнорировать возвращаемое значение. Если процедура возвращает значение, то тип значение записывается перед именем процедуры, как показано на рис. A-6. Аналогично параметрам, процедуры не могут возвращать массивы, структуры или процедуры, но могут вернуть указатели на них. Это правило разработано для более эффективной реализации — все параметры и результаты всегда соответствуют одному машинному слову (в котором хранится адрес). Компиляторы, которые допускают использование структур в качестве параметров, обычно также допускают их использование в качестве возвращаемых значений.

int sum (i, j)        /* эта процедура возвращает целое число */
int i, j ; 	          /*объявление формальных параметров */
{
      return (i + j);       /* добавить параметры и вернуть сумму */
}

Рис. А-6. Пример простой процедуры, которая возвращает значение.

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

printf («x=% d y = %o z = %x \n», x, y, z);

Первый параметр — это строка символов между кавычками (на самом деле это массив символов).

Любой символ, который не является процентом, просто печатается как есть.

Когда встречается процент, печатается следующий параметр в виде, определяемом буквой, следующей за процентом:
d — вывести в виде десятичного целого числа
o — печатать как восьмеричное целое
u — печатать как беззнаковое десятичное целое
x — печатать как шестнадцатеричное целое
s — печатать как строку символов
c — печатать как один символ
Также допускаются буквы D, 0 и X для десятичной, восьмеричной и шестнадцатеричной печати длинных чисел.

А.5. Выражения


Выражения создаются путем объединения операндов и операторов.

Арифметические операторы, такие как + и -, и реляционные операторы, такие как <
и > похожи на своих аналогов в других языках. Оператор %
используется по модулю. Стоит отметить, что оператор равенства это ==, а оператор неравенства это! =. Чтобы проверить равны ли a и b, можно написать так:

if (a == b) <оператор>;

Си также позволяет объединять оператор присваивания с другими операторами, поэтому

a +=  4;

эквивалентно записи

а = а + 4;

Другие операторы также могут быть объединены таким образом.

Си имеет операторы для манипулирования битами слова. Разрешены как сдвиги, так и побитовые логические операции. Операторы сдвига влево и вправо являются <<
и >> соответственно. Побитовые логические операторы &, | и ^, которые являются логическим И (AND), включающим ИЛИ (OR) и исключающим ИЛИ (XOP) соответственно. Если i имеет значение 035 (восьмеричное), тогда выражение i & 06 имеет значение 04 (восьмеричное). Еще один пример, если i = 7, то

j = (i << 3) | 014;

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

Если p является указателем на целое число, а i является целым числом, оператор

p = &i;

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

Другими словами, в качестве унарного оператора за звездочкой следует указатель (или
выражение, дающее указатель), и возвращает значение элемента, на который указывает. Если i имеет значение 6, то оператор

j = *р;

присвоит j число 6.
Оператор! (восклицательный знак – оператор отрицания) возвращает 0, если его операнд отличен от нуля, и 1, если его оператор равен 0.

Он в основном используется в операторах if, например

if (!x) k=0;

проверяет значение х. Если x равен нулю (false), то k присваивается значение 0. В действительности, оператор! отменяет условие, следующее за ним, так же, как оператор not в Паскаль.

Оператор ~ является побитовым оператором дополнения. Каждый 0 в своем операнде
становится 1, а каждый 1 становится 0.

Оператор sizeof сообщает размер его операнда в байтах. Применительно к
массиву из 20 целых чисел a на компьютере с 2-байтовыми целыми числами, например sizeof a будет иметь значение 40.

Последняя группа операторов — это операторы увеличения и уменьшения.

Оператор

р++;

означает увеличение р. На сколько увеличится p, зависит от его типа.
Целые числа или символы увеличиваются на 1, но указатели увеличиваются на
размер объекта, на который указывает Таким образом, если а является массивом структур, а р указатель на одну из этих структур, и мы пишем

p = &a[3];

чтобы заставить p указать на одну из структур в массиве, то после увеличения p
будет указывать на a[4] независимо от того, насколько велики структуры. Оператор

p--;

аналогичен оператору p++, за исключением того, что он уменьшает, а не увеличивает значение операнда.

В операторе

n = k++;

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

n = ++ k;

сначала увеличивается k, затем его новое значение сохраняется в n.

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

Последний оператор – это? (знак вопроса), который выбирает одну из двух альтернатив
разделеных двоеточием. Например, оператор,

i = (x < y ? 6 : k + 1);

сравнивает х с у. Если x меньше y, тогда i получает значение 6; в противном случае переменная i получает значение k + 1. Скобки не обязательны.

А.6. Структура программы


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

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

extern int v;

Каждая глобальная переменная должна быть объявленным ровно один раз без атрибута extern, чтобы выделить память под нее.

Переменные могут быть инициализированы при объявлении:

int size = 100;

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

А.7. Препроцессор Cи


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

1. Включение файлов.
2. Определение и замена макросов.
3. Условная компиляция.

Все директивы препроцессора начинаются со знака числа (#) в 1-ом столбце.
Когда директива вида

#include  "prog.h"

встречается препроцессором, он включает файл prog.h, строка за строкой, в
программу, которая будет передана компилятору. Когда директива #include написана как

#include <prog.h>

то включаемый файл ищется в каталоге /usr/include вместо рабочего каталога. В Cи распространена практика группировать объявления, используемые несколькими файлами, в заголовочном файле (обычно с суффиксом .h), и включать их там, где они необходимы.
Препроцессор также позволяет определения макросов. Например,

#define BLOCK_SIZE 1024

определяет макрос BLOCK_SIZE и присваивает ему значение 1024. С этого момента
каждое вхождение строки из 10 символов «BLOCK_SIZE» в файле будет
заменяться 4-символьной строкой «1024» до того, как компилятор увидит файл с программой. По соглашению имена макросов пишутся в верхнем регистре. Макросы могут иметь параметры, но на практике немногие это делают.

Третья особенность препроцессора — условная компиляция. В MINIX есть несколько
мест, где код написан специально для процессора 8088, и этот код не должен включаться при компиляции для другого процессора. Эти разделы выглядят как так:

#ifdef i8088
      <объявления только для 8088>
#endif

Если символ i8088 определен, то операторы между двумя директивами препроцессора #ifdef i8088 и #endif включаются в выходные данные препроцессора; в противном случае они пропускаются. Вызывая компилятор с командой

cc -c -Di8088 prog.c

или включив в программу заявление

#define i8088

мы определяем символ i8088, поэтому весь зависимый код для 8088 быть включен. По мере развития MINIX он может приобрести специальный код для 68000s и других процессоров, которые будут обрабатываться также.

В качестве примера того, как работает препроцессор, рассмотрим программу рис. A-7 (a). Она включает в себя один файл prog.h, содержимое которого выглядит следующим образом:

int x;
#define MAXAELEMENTS 100

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

cc  -E  -Di8088 prog.c

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

Именно этот вывод, а не исходный файл, дается как вход в Cи компилятор.

#include prog.h 			        int x;
main ()						main ();
{						{
      int a[MAX_ELEMENTS]; 			   int a [100];
      х = 4;					   х = 4;
      a[x] = 6; 				   а[х] = 6;
#ifdef i8088 					   printf("8088. a[x]:% d\n", a[x]);
      printf ("8088. a[x]:% d\n", a[x]);
#endif						}

#ifdef m68000
      printf ("68000. x=%d\n", x);
#endif
}
            (а) 				     (b)

Рис. А-7. (a) Содержание файла prog.c. (b) Выход препроцессора.

Обратите внимание, что препроцессор выполнил свою работу и удалил все строки, начинающиеся со знаком #. Если компилятор был бы вызван так

cc -c  -Dm68000 prog.c

то была бы включена другая печать. Если бы он был вызван вот так:

cc -c prog.c

то ни одна печать не была бы включена. (Читатель может поразмышлять о том, что случилось бы, если бы компилятор вызывался с обоими флагами -D?ags.)

А.8. Идиомы


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

while (n--) *p++  =  *q++;

Переменные p и q обычно являются символьными указателями, а n является счетчиком. Цикл копирует n-символьную строку из места, на которое указывает q, в место, на которое указывает р. На каждой итерации цикла счетчик уменьшается, пока он не доходит до 0, и каждый из указателей увеличивается, поэтому они последовательно указывают на ячейки памяти с более высоким номером.

Еще одна распространенная конструкция:

for (i = 0; i < N; i++) a[i] = 0;

которая устанавливает первые N элементов а в 0. Альтернативный способ написания этого цикла выглядит так:

for (p = &a[0]; p < &a[N]; p++) *p = 0;

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

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

if (a = f (x))  < оператор >;

сначала вызывает функцию f, затем присваивает результат вызова функции a и
наконец, проверяет, является ли оно истинным (ненулевым) или ложным (нулевым). Если а не равно нулю, то условие выполнено. Оператор

if (a = b) < оператор >;

также сначало значение переменной b переменной a, а затем проверяет a, не является ли значение ненулевым. И этот оператор полностью отличается от

if (a == b)  < оператор >;

который сравнивает две переменные и выполняет оператор, если они равны.

Послесловие


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

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


  1. FForth
    18.08.2019 15:06

    1. jobless
      18.08.2019 16:42

      pdf не считается… Хранится в ранге ценных документов в укромном месте

      Фото
      image


      1. saipr Автор
        18.08.2019 16:45

        Ну, межмодульные связи издавались и во Всесоюзном издательсте под названем "Комплексирование программ в ОС ЕС".
        Напишем, проблем нет.


        1. jobless
          18.08.2019 16:54
          +1

          Держал в руках и это издание. Просто недавно вспоминал книгу в диалоге, и тут же мироздание вывело на ваш комментарий на хабре, а уж моё любопытство завело к вам музей на сайте. :)


          1. saipr Автор
            18.08.2019 17:09

            Здоровое любопытство — это здорово!


    1. kobor
      18.08.2019 20:16

      Есть и посвежее издания. «Полный справочник по С». 4-е издание. Герберт Шилдт.
      Хорошо организованный справочник и для начального ознакомления и для практической работы. Практически полное описание языка и библиотек.


  1. JekaMas
    18.08.2019 15:09
    +1

    Всё же это тоже ценность, когда язык можно прочитать и выучить весь. 20-30 или около этого страниц — это здоворо!


  1. A1EF
    18.08.2019 15:25
    +1

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


  1. NeoCode
    18.08.2019 16:56
    +2

    Прямо сейчас изучаю исходники компилятора и линкера SCC, на фоне огромных исходников компилятора языка D совсем маленькая программа, десяток файлов общим объемом 140К, и эта кроха генерирует работающие Windows программы:)
    Конечно в Си есть некоторые архитектурные недостатки, понятно что на момент его создания еще не было такой отработанной базы концепций и парадигм как сейчас. Но такой маленький объем и простота — вот к чему надо стремиться.


    1. b-s-a
      20.08.2019 09:46
      +1

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


  1. oam2oam
    18.08.2019 17:20

    Я программирую на языке С уже 31 год. Изучал, к счастью, по K&R… Должен сказать, что считаю что это третий по важности язык, на котором стоит уметь писать — самый лаконичный и деловой… Но вот ни одна книга ( и ни по какому другому даже языку) не производит такого впечатления, как K&R — это, видимо, бессмертный шедевр. Эта книга и tiny C compiler — самое малое по объему (tcc имеет менее 200_000 строк текста весь) и самое глубокое, что только создано талантливыми людьми. Увы, после них невольно трудно воспринимать другие произведения, хотя и очень достойные.


    1. ilynxy
      18.08.2019 19:22

      А какие первые два языка по важности?


      1. oam2oam
        18.08.2019 19:29

        ну конечно, у каждого они свои… для меня на первом месте остается Пролог, а на втором — Ада…


        1. ilynxy
          18.08.2019 19:57

          Пролог — это понятно. А вот почему Ада? Чем разительно один процедурный язык отличается от другого?


          1. oam2oam
            18.08.2019 20:35
            +1

            Пролог — если мне удается на нем выразить задачу, то она легко решается на других языках. А вот Ада… тут не все так просто — этот язык появился ведь в 1983 году и с тех пор, как только что-то новое в нем появляется — лет так через… ну не знаю (иногда — никогда) ну скажем 20-30 это становится обсуждаемым и популярным.
            И еще, каждый язык создается под каким-нибудь типа лозунгом. Так вот, для Ады главное — это чтобы программы легко читались! А не писались! Этого, как мне кажется, не могут понять многие и многие программисты…


            1. saipr Автор
              18.08.2019 20:43

              Так значит прав Эндрю Таненбаум, когда писал:


              Акцент делается на чтении программ на Си, а не на написании кода.


              1. oam2oam
                18.08.2019 20:57
                +2

                Совершенно прав… Только вот читать программы на С невозможно :(
                Я когда-то студентам предлагал прочитать, что это такое:

                ((void(*)())0x02)()

                — согласитесь, не очень-то читается :)


                1. staticmain
                  18.08.2019 21:03
                  +1

                  За подобное в коде надо резать руки. Код на любом языке должен быть читаемым. Если выразительности языка не хватает/необходимо вставить что-то хакатическое должны быть комментарии.

                  Например:
                  Плохо:

                  reg1 = *(u32 *)data;

                  Лучше:
                  reg_bluelamp = flow2reg(user_input);


                  Если у вас в коде недокументированная или неотсемантиченная `((void(*)())0x02)()` то это плохой, негодный код. Так-то можно и на perl нечитаемый патч Бармина нарисовать.


                  1. oam2oam
                    18.08.2019 21:33

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


                  1. Lex20
                    19.08.2019 10:58

                    Мне сразу понятно что вызов пустой функции без аргументов по адресу 2


                1. saipr Автор
                  18.08.2019 21:07

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


                  1. oam2oam
                    18.08.2019 21:32

                    Верно. Кусок выше, конечно, неполный — там дальше было "=my_inr" и было это, да, установкой вектора прерывания. А весь исходный текст должен был быть меньше 1К…


                  1. mikkoljcov
                    19.08.2019 06:08

                    Работа над языком Си началась в 1969 в рамках написания с Кеном Томпсоном игры "Космический полёт". Одновременно Томпсон запилил свой Юникс, изначально не связанный с Си и Ричи никак. Когда язык был уже готов и представлял собой шедевр, в 1972, Томпсон попросил взять его для. 3й редакции Юникса, а 4ю просто переписал на Си (90% сишного кода). У меня такий сведения про появление Си.


                    1. TheCalligrapher
                      20.08.2019 03:00

                      Язык С не представлял собой никакого "шедевра" в 1972, а являлся в то время довольно неудачной поделкой, сделанной впопыхах и на коленке. Главным образом именно поэтому уже первое издание K&R полностью переработало и синтаксис, и семантику языка, отказавшись от большого числа "странных" низкоуровневых свойств языка С образца начала 1970х. С некоторой натяжкой "шедевром" можно назвать K&R C времен первого издания книги. Но настоящий С родился только со вторым изданием K&R, появившимся почти одновременно со стандартом C89/90.


                      1. saipr Автор
                        20.08.2019 10:03

                        Это естественно: ребенок родился (вспомните уровень развития ЭВТ в 1972 году и сравните сегодняшний день, спустя 50 лет, когда рассуждаем), начал учиться ходить, подрос, учился дальше и т.д. Как из гадкого утонкаа рождается прекрасный лебедь. Так и с Си.


                1. TheCalligrapher
                  20.08.2019 18:45

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


            1. FForth
              19.08.2019 13:08

              А, где, если это возможно, посмотреть на результаты решения Ваших и каких задач на Пролог? И какие Пролог инструменты сейчас в ходу?

              P.S. В своё время не смог осилить книгу Стерлинг Л, Шапиро Э «Искусство программирования на языке Пролог»


              1. oam2oam
                19.08.2019 14:32

                SWI-prolog — вот уже четверть века с нами… А из книг до сих пор лучшая — Братко «Программирование на Пролог». А результаты — так как задача возникает, так и пишу — сейчас на Прологе работаю над сквозным планированием для производства…


        1. saipr Автор
          18.08.2019 20:15

          Интересно, а это книгу вы читали:
          image
          От Паскаля к Аде/ Т.Ю.Бардинова, В.Ю.Блажнов, А.А.Маслов, В.Н.Орлов. — 1990
          Москва, Финансы и статистика, 1990. 255 с. Твердый переплет.


          1. oam2oam
            18.08.2019 20:38

            Нет. Для меня любимой является книга Органика про систему i432… А от Паскаля к Аде не перейти никак, мне кажется… вот от Ады, да, легко. При всей схожести синтаксиса идеи языков противоположные (я выше уже там отметил — для Ады главное, чтобы легко читалось).
            Я иногда просто описывал на английском языке (ну сокращенном) задачу — а компилятор Ады потом не находил в тексте ошибок :)


            1. Tollanin
              19.08.2019 14:41

              Извиняюсь за пикантный вопрос, но я чуть-чуть не застал времена популярности Ады и полагал, что это «академический» язык (т.е. для обучения студентов). Не подскажете, были ли в СССР серьёзные проекты, реализованные на Аде?


              1. saipr Автор
                19.08.2019 15:03

                Я думаю нет, не было. И мы, когда начали создавать СИМ/САИПР не рассматривали его как язык разработки. С нас требовали ПЛ/1. В СССР пик популярности Ады совпал не только с развалом СССР, но с началом продвижения языка Си. А с ним Ада поспорить не могла, прежде всего из-за отсутствия компиляторов. И тут наступила эра персоналок и MS Windows и, естественно, Си. И Ада канула в Лету.


              1. oam2oam
                19.08.2019 19:13
                +1

                Вы ещё не застали времена популярности Ады :) Но ни в СССР ни в России я не знаю о серьёзных проектах… Это во Франции и США он прочно занимает свою нишу (с о-о-очень серьезными проектами — от крылатых ракет и космических систем до метро и атомных станций). У нас почти нет нужды в серьезных системах — промышленности-то почти нет, а что есть — там все устарело…
                А теперь вот пришла пора Аде на микроконтроллерах цвести — по иронии, она на них и планировалась — я тут даже статью написал как дрыгать ногой на Аде Адское программирование голого железа


                1. saipr Автор
                  19.08.2019 19:24
                  +1

                  Вы сто раз правы:


                  У нас почти нет нужды в серьезных системах — промышленности-то почти нет, а что есть — там все устарело…

                  У нас принимают законы на полном серьезе, как шрафовать просто за курение, а не за то, что по чьей-то вине (кто-то позволил за копейки лес вырубить, кто-то не почистил русла рек, да малоли чего) затапливает города.
                  Ай, даже не хотется обсуждать это. Была бы серьезная потребность, давно бы и ОС свои были!!


                  1. Source
                    20.08.2019 01:16

                    Ну, как минимум, одна российская ОС уже есть.


                    P.S. Хотя чего это я, ещё одну вспомнил — Kolibri OS


      1. saipr Автор
        18.08.2019 20:08
        -3

        Вообще-то, если ты программист, то какая разница на каком языке писать код!


        1. oam2oam
          18.08.2019 20:39
          +1

          Оказывается, очень большая! Я, к счастью, математик и это отлично знаю…


          1. saipr Автор
            18.08.2019 20:46

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


            1. oam2oam
              18.08.2019 20:54
              +1

              Это-то верно!


        1. dim2r
          19.08.2019 11:12

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


          1. saipr Автор
            19.08.2019 11:23

            Если он профессиональный программист, то он прочитает описание языка


            1. monah_tuk
              20.08.2019 03:53

              Написать — сможет. Эффективно, быстро, читабельно, безопасно — это уже вопрос.


              Я уже не раз сталкивался, как люди пишут, я читаю, говорю: у тебя тут UB, на что ответ: "Ну и что? Работает же!" А потом… другая ОС, другой компилятор и… А почему это не работает. Или… просто компилятор тот же обновился.


              1. dim2r
                20.08.2019 14:17

                Мне кажется, что если хорошо знать с++, то остальные языки будет намного легче понять. Хотя может возникнуть эффект неприятия со стороны других разработчиков. Например, когда я спрашиваю джавистов — за сколько тактов происходит адресация объекта или какие барьеры памяти используются в volitale? Они просто не понимают вопросов, но как заколдованные твердят «high performance»,«low latency».


                1. Yuuri
                  20.08.2019 23:45
                  +1

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

                  И даже Forth, Prolog, Erlang или Haskell?
                  Вообще, по моим наблюдениям, люди, утверждающие в стиле «хорошему программисту должно быть без разницы, на каком языке писать», зачастую просто плохо представляют, насколько другими могут быть языки.


                  1. monah_tuk
                    21.08.2019 04:14

                    Точнее, насколько иные подходы и идиомы они могут использовать. Есть шутка (или только частично шутка), что стоит выучить хаскель, что бы более эффективно и безопасно писать на C++ ;-)


                    Да что там Хаскель, на одном C++ можно писать настолько по разному, что люди с трудом смогут понять друг друга.


                1. monah_tuk
                  21.08.2019 04:18

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


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


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


  1. jobless
    18.08.2019 18:07

    Безнадёжно пытаюсь вспомнить, как (без интернета) я на рубеже 80-90х узнал о существовании Си Транслятора от НИЦЕВТ ( www.nicevt.ru ). Уж точно не помню, платила ли моя контора какие нибудь деньги, но после моей настойчивости письмами официальными точно обменивались и забирал я ленту в здании во дворе за основным монстром построенном по типовому проекту школы того времени. То что можно назвать stdlib было в исходниках и причём безобразного качества. Помню потому, что переписывал для себя под пара виртуализацию VM(CMS) [СВМ(ПДО)].
    — В статье, автор упомянул PL/1, на днях читая на опеннете новость про Rust 1.37 "… Дополнительно можно отметить начало тестирования проекта Async-std, предлагающего асинхронный вариант стандартной библиотеки Rust (порт библиотеки std, в котором все интерфейсы предложены в версии с async и готовы для использования с синтаксисом async/await). " в очередной раз вспомнил как был в середине 80х бит(фигурально) в курилке за асинхронный ввод вывод при работе с лентой на PL/1, ибо лента крутится быстро быстро, а терминалы с Primus все висят. ))))


    1. saipr Автор
      18.08.2019 20:06
      +1

      Безнадёжно пытаюсь вспомнить, как (без интернета) я на рубеже 80-90х узнал о существовании Си Транслятора от НИЦЕВТ ( www.nicevt.ru ).

      В 1987 году мы начали создавать СМИ (стенд имитационного моделирования по программ) антиСОИ:
      image
      Встал вопрос на чем разраатывать? Мой ответ был — это будет Unix и язык Си.
      Ни того ни другого я еще не держал в руках. Все упивались PL/1 и ОС ЕС.
      Все страшно удивились, как так, по твоим книжкам учат программированию на PL/1.
      Но я стоял твердо: либо так либо я не берусь за это дело. И знаете где мы взяли и Си и Unix.
      Юних для больших ЕС ЭВМ мы взяли в НИЦЭВТ-е под названием МОС ЕС и там уже был Си. А для первых персоналок ЕС-1840 — это был Minix от Эндрю Таненбаума с компилятом Си. Вс это было в 1987 году. Вы не поверите, прошло лет 15 и нашлись люди, которые подошли по мне и сказали как здорово, что вы настояли тогда на Юниксе и Си, а то где бы мы были со смоим ПЛ/1.
      Вот такая история


      1. jobless
        20.08.2019 08:36

        Через страничку FreeDos на FB мироздание вывело www.hampa.ch/pce/download.html
        С такими замечательными игрушками помимо прочих.
        pce-0.2.2-xt-xenix86-2.1.3.zip An IBM PC/XT 5160 with SCO Xenix System V 2.1.3
        pce-0.2.2-xt-minix-1.1.zip An IBM PC/XT 5160 with MINIX 1.1
        pce-0.2.2-xt-minix-2.0.2.zip An IBM PC/XT 5160 with MINIX 2.0.2
        P.s. Кстати Xenix видел живьём, ставил не я, но совсем чуть чуть по клавиатуре по ходил пальцами. В память врезалось удивление двухпанельным файловым менеджером (mc ли это, не помню) на янтарном геркулесе.


        1. saipr Автор
          20.08.2019 10:16

          P.s. Кстати Xenix видел живьём

          Я на нем работал. Были первые первые персоналки PC/XT. Как я уже сказал, все решено было делать на Unix, но мы никак не могли получить во НИЦЭВТ МОС ЕС для персоналок и тогда было решено начинать разработку на Xenix. Хорошая система была. Нашими исполнителями была группа разработчиков из ИПМ АН СССР, возглавляемая профессором Трахтенгерцом Э.А.


  1. jobless
    18.08.2019 20:49
    -1

    Владимир Николаевич, вы меня заставили вспомнить страшное. Я не вспомнил, откуда узнал о Ницевтомском Си под ОС ЕС, но вспомнил другое, за давностью лет, думаю можно публично ))).
    Вероятно с оплатой или ещё с какими то официальными действиями проблемы таки возникли и интересно это было мне одному, а транслятор достался как бы с временной лицензией. Т.е. стояла проверка на дату после которой он должен перестать работать. Большая красивая ОС СВМ(ПДО) на ЕС1060 мне позволила найти это место и УКРАСТЬ(каюсь публично) Си у НИЦЕВТа. Может и было потом что то оформлено, не помню, моя судьба вместе с судьбой страны резко поменялась.
    Если продолжить вечер воспоминаний, то выше я про PL/1 и ленту писал. Так вот лента была с телеметрией «Изделие 171» до перевода испытаний на Камчатку.
    Ну а с Unix(ДЕМОС) ещё веселей, уже будучи почти свободным не советским «художником», за наличные рубли одного маленького банка, покупал у М.Коротаева дистрибутив для СМ1425(pdp1173) в знаменательный день. В день когда Демосу(фирме) отрубили канал, утром был у них в офисе, не зная, что Михаила нужно искать в АО Релком, которому и перешли права на канал в Европу. Поздно вечером, мы таки встретились в офисе Релкома и я попал на фуршет по случаю. )))
    p.s. И это всё про Си из прошлого века…


    1. saipr Автор
      18.08.2019 20:54

      Кстати, помимо МОС ЕС, Демос, был ведь еще и Инмос. Вон сколько оказывается ЮНИК-ов было в СССР!


  1. jobless
    18.08.2019 21:18

    Для того, что бы влюбится в си, нужно иметь несколько лет опыта Ассемблеров (ЕС)IBM360/370,(СМ4)PDP11,(СМ2)[на моторолу похоже], захватить немного Урал и Днепр в кодах, начать разбираться с i8080,i8086, ещё немножко мелочёвки типа дизассемблирования автокода УПД на ГМД (устройство подготовки данных на гибких магнитных дисках — процессор 8-разрядный собранный из 4 2х-разрядных) с целью понять как оно с каналом Большой машины работает и потом заменить на «писюк», совсем чуть чуть захватить ЯМБ на НЕВА501 и тут вам бац СИ. Т.е. вам больше не нужно помнить всё что вы помнили до этого. У нас вот это «текст на Си плохо читается» вызывает в лучшем случае улыбку.


    1. saipr Автор
      18.08.2019 22:03

      Прямо про меня. Только бы добавил Весну и СПЭМ-80, М-220 и Мир, а также автокод и Алмо, и кто тут говорит, что «текст на Си плохо читается»?


      1. jobless
        18.08.2019 22:33

        > "… Только вот читать программы на С невозможно :(… "
        habr.com/ru/post/464075/#comment_20521017

        p.s. с цитированием я пока не разобрался… :(
        p.p.s С первых дней знакомства у меня была необходимость и возможность благодаря Си писать сразу для 32-х,16-ти,8-ми — разрядных разных систем(и железо и ос) на одном языке и это было волшебство!!! Это забыть не возможно.


        1. saipr Автор
          18.08.2019 22:39

          Спасибо, но это я понял. Вопрос был риторическим!


    1. Zuy
      19.08.2019 04:29

      Мне кажется после любого ассемблера СИ воспринимается, как огромное счастье. У меня были Z80, PIC и i8086. СИ после них это как другой мир.


    1. pfihr
      19.08.2019 09:52
      +1

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


      1. saipr Автор
        19.08.2019 10:23

        Как я вас понимаю.
        Вот этим и хороща была книга Эндрю Таненбаума «Operating Systems Design and Implementation» ее первое издание, что в ней было описание и Си и процессора 8088 (стр.363-370). Кстати, тоже можно перевести.


      1. Keynessian
        19.08.2019 11:47

        Так это ты pfihr вырезал в журнале, то что я надеялся прочитать в библиотеке?!


  1. dmxvlx
    18.08.2019 23:10

    в C99 есть инициализация полей структуры по их именам:


    struct mystuct {int  a; long b;}; 
    
    struct mystuct ms = {.a=1, .b=2}; // C99 initializing by names for more readable
    
    struct mystuct ms2 = {1, 2}; // general initializing by values
    
    struct mystuct ms3 = {a:1, b:2}; // obsolete initializing by names

    PS: чаще использую инициализацию по значению, хотя более читабельно по именам конечно же...


    1. NeoCode
      18.08.2019 23:20

      Отличная фича, вот пример того что есть в Си и чего очень не хватает в С++ (в с++20 вроде занесут, но все равно приходится пользоваться более старыми компиляторами).


      1. mapron
        19.08.2019 00:49

        приходится пользоваться более старыми компиляторами

        Если речь о GCC/Clang, то с ними проблем и нет, только придется собирать в режиме gnu а не std. с Visual C++, да, беда.


      1. dmxvlx
        19.08.2019 00:52

        у нас ведь уже есть initializer_list (с оговоркой того что только по значению инициализация а не по именам, ну и как бэ -std=C++11)
        я сам ещё в своих поделках не применял, но весчь оч полезная ;)


      1. rogoz
        19.08.2019 02:30
        +1

        По моему чуть ли не единственная фича, которая хороша, есть в C, и её нет в C++.
        К сожалению даже в C++20 она пока выглядит не тортово:

        struct mystuct {int  a; long b;}; 
        struct mystuct ms = {.a=1, .b=2}; //как в С, всё ок
        struct mystuct ms2 = {.b=1, .a=2}; //а хрен тебе, а не как в С
        //error: designator order for field 'mystuct::a' does not match declaration order in 'mystuct'
        struct mystuct ms3 = {.a=1};
        struct mystuct ms4 = {.b=1}; // так нормально


        1. assembled
          19.08.2019 12:04

          В сях еще есть синтаксис для инициализации массивов:

          char map[256] = {['1'] = 1, ['2'] = 2,};
          Удобно когда нужно инициализировать только некоторые элементы.
          В крестах такого нет.

          Еще по-моему в крестах нет __VA_ARGS__.

          Вобщем кресты — говно.


          1. rogoz
            19.08.2019 14:22

            Еще по-моему в крестах нет __VA_ARGS__.
            С C++11 есть.


          1. TheCalligrapher
            20.08.2019 02:49

            Ради формальной корректности стоит заметить, что ни в С, ни в С++ не существует способа "инициализировать только некоторые элементы" агрегата через посредство {}-инициализатора в объявлении. Инициализированы в вашем примере будут все таки все элементы без исключения. Т.е. элементы, для которых вы не указали инициализатора, будут инициализированы нулями.


            1. assembled
              20.08.2019 11:19

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


              1. TheCalligrapher
                20.08.2019 21:48
                +1

                Это верно. Но во некоторых случаях это выливается в неприятный удар по производительности кода у ничего не подозревающего автора. Что-то вроде


                char buffer[1024] = { [0] = '?' };

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


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


                1. saipr Автор
                  20.08.2019 22:59

                  Вспоминается програмирование на М-220, где было 4К оперативки. Вот там проявлялоь мастерство программирования.


        1. monah_tuk
          20.08.2019 03:58

          struct mystuct ms2 = {.b=1, .a=2}; //а хрен тебе, а не как в С
          //error: designator order for field 'mystuct::a' does not match declaration order in 'mystuct'

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


          1. NeoCode
            20.08.2019 07:10

            Это странно. Какая разница в каком порядке инициализировать поля?


            1. TheCalligrapher
              20.08.2019 08:08

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


  1. mikkoljcov
    19.08.2019 06:01

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


    1. saipr Автор
      19.08.2019 12:45

      Но какая наводка!


  1. surly
    19.08.2019 12:40
    +1

    Ещё стоило бы упомянуть особенность, про которую мало где пишут: <условие> в условных операторах и циклах может отсутствовать, тогда оно считается истинным. Именно благодаря этой фиче конструкция for(;;) { ... } работает как бесконечный цикл с выходом из середины.
    Ерунду написал. Проверил в GCC; оказывается, пустое условие разрешено только в «for». В while и if пустое условие вызывает ошибку синтаксиса.


  1. TheCalligrapher
    20.08.2019 00:13

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


    Кстати, вы перевели термин "statement" как "оператор", что является неприятной особенностью многих переводов книг по С и С++ на русский язык. Возникающая при этом неоднозначность между терминами "statement" и "operator" совершенно неприемлема в серьезной литературе. Также, в начале "А.4. Операторы" вы внезапно перевели один из "statement" как "утверждение", что только добавило путаницы.


    1. saipr Автор
      20.08.2019 09:56

      Книга представляет лишь исторический интерес

      Но ведь именно об этом и сказано в статье:


      Это описание интересно также и с исторической точки зрения и для понимания того, как далеко ушел язык Си с момента своего рождения и IT-отрасль в целом.

      Только без всякого лишь.
      Насчет "утверждения" и "оператор" полностью с вами согласен и поправил. Спасибо.
      А вот насчет


      как и в K&R, львиная доля изложенной информации уже давно относится к категории "это было давно и не правда"

      позволю с вами не согласиться. Классика бессмертна.


      1. assembled
        20.08.2019 11:52

        Классика бессмертна.

        Я немного удивился, когда узнал что компиляторы до сих пор поддерживают такой синтаксис:
        int main(argc, argv)
        int argc;
        char *argv[];
        Но так уже почти никто не пишет.

        Еще неявный int был убран из C99, но от компиляторов больше варнинга ничего не увидишь. И так тоже не пишут.

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

        З.Ы. Почему то в старых сорцах сорцах постоянно вижу выражение у return в скобках, несовместимости конечно никакой нет, но просто интересно, зачем так делали? Это синтаксис раньше такой был? Или это чтобы return можно было макросом заменить?


        1. saipr Автор
          20.08.2019 12:56

          Я немного удивился, когда узнал что компиляторы до сих пор поддерживают такой синтаксис:
          int main(argc, argv)
          int argc;
          char *argv[];

          Я тоже удивился и бросился проверять. Проверил и успокоился: преемственность обеспечена!


        1. TheCalligrapher
          20.08.2019 15:27
          +1

          Этот синтаксис (так назывемый "K&R синтаксис") объявления функции был официально объявлен obsolescent (т.е. подлежащим удалению из языка) начиная с самого первого стандарта С. Однако ни у кого не поднимается рука его фактически удалить, ибо обратная совместимость с тоннами старого кода до сих пор важна, а переписывать его никто не хочет.


          Кстати, я смотрю, кого-то удивила продолжающаяся поддержка этого синтаксиса. Но на самом деле всякий раз, когда в программе на С вы описываете параметры функции как () (пустые скобки), вы используете именно этот устаревший K&R синтаксис. Это его частный случай. В современном синтаксисе не допускается (), а требуется именно (void).


          Неявный int действительно убран из языка. И в языке С нет "варнингов", а есть только диагностические сообщения. Как только компилятор выдал требуемое стандартом диагностическое сообщение в ответ на некорректный код, язык С на этом заканчивается. Дальше остаётся только самодеятельность вашего компилятора, к языку С никакого отношения не имеющая. Современные компиляторы С вам и указатели никак не связанных типов тоже разрешат неявно друг к другу приводить (с "варнингом"), хотя язык С это явно запрещает.


          Это ключевой момент в поведении большинства современных мэйнстримовых компиляторов С: в первую очередь это компиляторы огромной по своему объему устаревшей и довольно низкокачественной базы унаследованного кода, написанной на некоем развеселом С-подобном языке. И только во вторую (если не в пятую) очередь эти компиляторы вспоминают собственно о самом чистом языке С. В частности, в gcc язык С начинается с флагов -std и -pedantic (лучше — -pedantic-errors). А то, что вы получаете в режиме по умолчанию — это не С, а некий студенческий С-подобный суржик.


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


          Интересным является утверждение "Каждая глобальная переменная должна быть объявленным ровно один раз без атрибута extern, чтобы выделить память под нее." Это действительно так и стандарт языка требует единственного определения. Однако компиляторы, произрастающие из Юникс (в частности, gcc), всегда игнорировали это правило и позволяли множественные определения переменных, раскиданные по программе. Компиляторы с других платформ как раз выступали за единственность определения, ибо глобальный поиск и удаление лишних определений — неоправданно трудоемкая операция (по меркам тех времен). В конечном итоге победили последние, а не юниксоиды: стандарт С возлагает заботу о единственности определения на автора кода.


          А что касается синтаксиса с операндом return в скобках — это было требованием в первых версиях языка С образца начала 70х. Отменилось это требование только в K&R. Скорее всего этот синтаксис было просто тупо унаследован из предшественников C: языков B и/или BCPL.