Введение в Си. Послание из прошлого столетия

    Предисловие


    Я несколько раз в своих комментариях ссылался на книгу Эндрю Таненбаума «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

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

    А.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)  < оператор >;

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

    Послесловие


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

    Подробнее
    Реклама

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

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

        И мечтаю о прочтении чего то современного на тему и в том же прикладном качестве (практической полезности) как:
        Межмодульные связи в ОС ЕС
        image
          0

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

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

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

                +1
                pl1.su/часто-задаваемые-вопросы
                Куда только не заводит здоровое любопытство.
                  0

                  Жив курилка:


                  В 1984 году при своем выпуске это был компилятор для MS DOS версии 1.0 и IBM-PC /XT.

                  Мы тоже в 1987 году искали исполнителя для разработки транслятора PL/1 для ЕС-184x. Но главное направление было все же Си. PL/1 мы так и не заказали и я был этому рад.

                    +1
                    Ну это всё таки совсем урезанный вариант.
                    В классическом высший пилотаж подзадачи, асинхронный ввод вывод, динамические массивы, базированные структуры с произвольным побитовым формированием полей и волшебным Структура A = Структура B BY NAME. Отложенная обработка (т.е. не критичная по времени) непрерывно от испытания к испытанию меняющейся структурой кадра телеметрии была праздничной. До того как я подсунул вот это BY NAME под каждое испытание писали и отлаживали фактически новую программу на ассемблере. А так описал места битовых кусочков информации в одной структуре и пригодные типы для последующей обработки в другой одинаковыми именами и пусть компилятор работает. А время было тяжёлое, 16-ти бит телеметристам не хватало катастрофически. В одном легендарном ЗАТО в середине 80х проводили конференцию по унификации и стандартизации и при подготовке насчитали 16 способов представления 16-ти разрядного отрицательного числа встреченного при обработке. т.е. задействовать все 16 разрядов а знак разместить там, где получится или вообще ни где, по контексту должны догадаться при обработке.
                    P.s. И за «Межмодульные связи в ОС ЕС» именно в этом издании снимаю шляпу. В числе самых полезных книг моей жизни она.
                      0

                      jobless :


                      И за «Межмодульные связи в ОС ЕС» именно в этом издании снимаю шляпу. В числе самых полезных книг моей жизни она.

                      Приятно, черт побери. Спасибо!

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

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

            +4

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

              +2
              Прямо сейчас изучаю исходники компилятора и линкера SCC, на фоне огромных исходников компилятора языка D совсем маленькая программа, десяток файлов общим объемом 140К, и эта кроха генерирует работающие Windows программы:)
              Конечно в Си есть некоторые архитектурные недостатки, понятно что на момент его создания еще не было такой отработанной базы концепций и парадигм как сейчас. Но такой маленький объем и простота — вот к чему надо стремиться.
                +2
                Посмотрите простейший компилятор ассемблера. А лучше сразу редактор двоичных файлов.
                Главное не сложность/простота компилятора, а простота программы на этом языке и качество создаваемого компилятором кода.
                +1
                Я программирую на языке С уже 31 год. Изучал, к счастью, по K&R… Должен сказать, что считаю что это третий по важности язык, на котором стоит уметь писать — самый лаконичный и деловой… Но вот ни одна книга ( и ни по какому другому даже языку) не производит такого впечатления, как K&R — это, видимо, бессмертный шедевр. Эта книга и tiny C compiler — самое малое по объему (tcc имеет менее 200_000 строк текста весь) и самое глубокое, что только создано талантливыми людьми. Увы, после них невольно трудно воспринимать другие произведения, хотя и очень достойные.
                  0
                  А какие первые два языка по важности?
                    0
                    ну конечно, у каждого они свои… для меня на первом месте остается Пролог, а на втором — Ада…
                      0
                      Пролог — это понятно. А вот почему Ада? Чем разительно один процедурный язык отличается от другого?
                        +2
                        Пролог — если мне удается на нем выразить задачу, то она легко решается на других языках. А вот Ада… тут не все так просто — этот язык появился ведь в 1983 году и с тех пор, как только что-то новое в нем появляется — лет так через… ну не знаю (иногда — никогда) ну скажем 20-30 это становится обсуждаемым и популярным.
                        И еще, каждый язык создается под каким-нибудь типа лозунгом. Так вот, для Ады главное — это чтобы программы легко читались! А не писались! Этого, как мне кажется, не могут понять многие и многие программисты…
                          0

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


                          Акцент делается на чтении программ на Си, а не на написании кода.
                            +3
                            Совершенно прав… Только вот читать программы на С невозможно :(
                            Я когда-то студентам предлагал прочитать, что это такое:
                            ((void(*)())0x02)()

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

                              Например:
                              Плохо:
                              reg1 = *(u32 *)data;

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


                              Если у вас в коде недокументированная или неотсемантиченная `((void(*)())0x02)()` то это плохой, негодный код. Так-то можно и на perl нечитаемый патч Бармина нарисовать.
                                0
                                код-то как раз отличный, в комментарии ниже я написал, почему он такой… Но только плохо читаемый… И, кстати, указатель на функцию никак лучше не сделаешь — такой уж С язык.
                                  0

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

                                  0

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

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

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

                                        +1

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

                                          0

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

                                      0

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

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

                                    P.S. В своё время не смог осилить книгу Стерлинг Л, Шапиро Э «Искусство программирования на языке Пролог»
                                      0
                                      SWI-prolog — вот уже четверть века с нами… А из книг до сих пор лучшая — Братко «Программирование на Пролог». А результаты — так как задача возникает, так и пишу — сейчас на Прологе работаю над сквозным планированием для производства…
                                  0

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

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

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

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

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


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

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

                                          0
                                          Ада — язык отнюдь не «академический», а самый что ни на есть военно-прикладной.
                                          Был разработан по заказу Пентагона для замены языка COBOL. К компиляторам предъявлялись очень жёсткие требования на соответствие стандарту, в частности для исключения возможности образования диалектов, что сильно затрудняло процедуру сертификации. На эту тему расскажу байку.

                                          В начале 90-х летел в самолёте. Мужик в соседнем кресле работает на ноутбуке (тогда ещё назывался лэптоп). Вижу, что у него в окошках тексты на Аде.
                                          Вежливо интересуюсь: «О, у Вас есть компилятор с Ады?».
                                          Гордо отвечает: «Это Я — компилятор с Ады!».
                                          Оказалось, что в их организации (не знаю, какое подразделение НАТО) требуют программировать на Аде, а сертифицированного компилятора нет, и чтобы как-то работать в качестве компилятора используют живого человека. Бывало и такое.
                                            0

                                            В начале 90-х компилятор с Ады был не проблемой. У нас он работал даже на ЕС 1841.

                                    0

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

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

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

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

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

                                            +1

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


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

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

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

                                                  +1

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


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

                                                  +1

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


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


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

                                            0
                                            Вообще-то, если ты программист, то какая разница на каком языке писать код!
                                            Сильное утверждение. Я бы его смягчил в духе Генри Форда («Any customer can have a car painted any color that he wants so long as it is black.») в такой редакции:

                                            Если ты программист, то какая разница на каком языке писать код,
                                            до тех пор пока это язык Си.
                                              0

                                              Именно потому, что я Программист и знаю Си, мне без разницы на каком языке писать.
                                              А про Генри Форда красиво.

                                                0
                                                Напишите что нибудь на Форт, Лисп или Пролог, если вы программист на Си и Вам, поэтому, безразлично на каком языке писать. :)

                                                P.S. В выше комментарии был именно акцент, пока язык Си (Си подобный) или не так?
                                                  0

                                                  Могу еще добавить tcl, perl, python (что там еще), modula, Алмо… И никакого акцента на Си. Я начал программировать тогда, когда еще не было Си в учебных программах.

                                          +1
                                          И в этом материале, и в книге из первой ссылки, и вот упомянутый вами «Язык программирования Си», и в Википедии — везде «Си», а не путающее «С». Не смогли привыкнуть за 31 год-то? Или в чём дело? Даже в вашем первом предложении кириллическое «С».
                                            0
                                            Совершенно верно — не привык и не собираюсь! Язык называется С, просто потому что надо знать его историю — вначале был A… а потом D… Когда пишут Си — это не более чем транслитерация, но, надеюсь, вы же не пишете в тексте транслитерацией? Или пишете? Или в чём дело?
                                              0
                                              Язык называется С
                                              Нет, язык называется C, вы снова использовали кириллицу, поэтому в вашем случае это «Эс» :)
                                                0
                                                не привык и не собираюсь!

                                                Никто вас не пренуждает!
                                                Вам просто советуют, если вы пишите C имея ввиду язык Си, то пишите C литинской буквой C (си), а не русской буквой C (эс)!

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

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

                                                0
                                                Через страничку 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 ли это, не помню) на янтарном геркулесе.
                                                  0
                                                  P.s. Кстати Xenix видел живьём

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

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

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

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

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

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

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

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

                                                    0

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

                                                      +1

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

                                                        0

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

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

                                                        в 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: чаще использую инициализацию по значению, хотя более читабельно по именам конечно же...

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

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

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

                                                                +3
                                                                По моему чуть ли не единственная фича, которая хороша, есть в 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
                                                                  В сях еще есть синтаксис для инициализации массивов:
                                                                  char map[256] = {['1'] = 1, ['2'] = 2,};
                                                                  Удобно когда нужно инициализировать только некоторые элементы.
                                                                  В крестах такого нет.

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

                                                                  Вобщем кресты — говно.
                                                                    +1
                                                                    Еще по-моему в крестах нет __VA_ARGS__.
                                                                    С C++11 есть.
                                                                      0

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

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

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


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

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


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

                                                                            0

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

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

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

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

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

                                                                  0

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

                                                                    0

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

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

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


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

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

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


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

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


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

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

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

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

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

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

                                                                          З.Ы. Почему то в старых сорцах сорцах постоянно вижу выражение у return в скобках, несовместимости конечно никакой нет, но просто интересно, зачем так делали? Это синтаксис раньше такой был? Или это чтобы return можно было макросом заменить?
                                                                            0
                                                                            Я немного удивился, когда узнал что компиляторы до сих пор поддерживают такой синтаксис:
                                                                            int main(argc, argv)
                                                                            int argc;
                                                                            char *argv[];

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

                                                                              +2

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


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


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


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


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


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


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

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

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