четверг, 25 сентября 2008 г.

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

Оригинал, по которому делается данный конспект, находится здесь.
  1. Создаем директорию;
  2. Создаем группу пользователей, которые будут работать с этой директорией;
  3. Создаем каждого пользователя с общей домашней директорией (пункт 1) и принадлежащих одной группе (пункт 2). Назначаем им пароли;
  4. Назначаем одного из этих пользователей и эту группу владельцами данной директории;
  5. Устанавливаем нужные права доступа данной директории;
  6. Устанавливаем у этой директории sgid бит. Это нужно для того, чтобы создаваемый в этой папке файл принадлежал группе-владельцу этой папки, а не группе пользователя, который создает файл;
Теперь все это в виде командной строки во FreeBSD:
  1. # mkdir /home/project1
  2. # pw group add project1
  3. # pw user add -n padmin -d /home/project1 -g project1 -m; passwd user.
    Если пользователь уже существует #pw user mod -n username -G project1;
  4. # chown -R padmin.project1 /home/project1
  5. # chmod -R 775 /home/project1
  6. # chmod -R 2775 /home/project1 или # chmod -R g+s /home/project1

пятница, 19 сентября 2008 г.

10 заповедей Си программисту от Henry Spencer

1. Ты должен пользоваться lint-ом почаще и внимать его изречениям с большим вниманием ибо воистину часто он видит больше тебя и его суждения более верны твоих.
2. Не следуй за null-указателем ибо хаос и безумие ждут тебя на том месте.
3. Ты должен явно приводить все аргументы функций к ожидаемому типу, если они не есть уже этим типом, даже если твое искусство убеждено, что это необязательно. Чтобы не постигла тебя более жестокая участь, чем ты ожидаешь.
4. Если твой заголовочный файл недостаточно точно описывает возвращаемый тип твоей библиотечной функций, то ты должен декларировать их с большей тщательностью, чтобы не случилось с твоей программой вопиющего зла.
5. Ты всегда должен следить за границами всех строк (а они в действительности массивы), ибо уж точно, что там где ты напечатаешь "foo", кто-нибудь когда-нибудь напечатает "supercalifragilisticexpialidocious".
6. Если функция была объявлена как возвращающая код ошибки, ты должен обработать этот код. О да. Даже если это утроит размер твоего кода и доставляет боль твоим печатающим пальцам. Ибо, если ты думаешь "это никогда не случиться со мной", то воистину будешь наказан за свою надменность.
7. Ты должен изучать твои библиотеки и прилагать усилия, чтобы не изобретать их повторно без причин, чтобы твой код мог быть коротким и читаемым, а дни твои были результативными и протекали в удовольствии.
8. Ты должен делать цели и структуру твоей программы ясной для твоих соратников использованием Единственно Истинного Стиля Ставить Скобки, нравится тебе это или нет. Ибо лучше, чтобы твои силы были используемы для решения проблемы а не для создания нового красивого препятствия для понимания.
9. Твои внешние идентификаторы должны быть уникальны только в пределах первых шести букв, хотя это неприятное правило надоело и годы его необходимости затянулись пред тобою, чтобы не рвал твои волосы и не доходил до безумия в тот роковой день, когда решишь запустить твою программу на старой системе.
10. Ты должен отречься от подлой ереси, которая провозгласила "весь мир - VAX" и не общаться с погруженными во тьму еретиками, которые цепляються за это варварское верование, чтобы дни твоей программы могли быть долгими даже не смотря на то, что дни твоей машины коротки.

вторник, 2 сентября 2008 г.

K&R конспект

Кто из нас, написав (вернее переписав) классическую программу "Hello, world!", не почувствовал себя программистом? А далее, после создания приложения выводящего окно на экран или табулирующего результаты расчетов, уже почти состоявшимся девелопером? И, тем не менее, даже когда мы создаем не совсем тривиальные приложения, мы можем не знать (или не осозновать?) многих тонкостей языка, на котором мы пишем. Особенно это касается языка Си. Потому что за его простотой кроется "мощь" и "гибкость". Так прост он или(и) мощен? На этот вопрос нет ответа. Как впрочем и ответа на вопрос: а что же такое "мощь", "гибкость" (и т.п.) предметов, если можно так выразиться, которые относятся к программированию? Хотя в айтишной литературе любят такие слова, но кроме "интуитивного", другого понимания нет. Программирование - это точная наука. А значит оно требует точных определений, а не размытых образов. Конечно же это мое имхо.
Иллюзию простоты языка Си придает тот факт, что основной труд, если хотите первоисточник, по языку Си а именно книга создателей Си Кернигана и Ритчи "Язык программирования Си" очень небольшой. Особенно если сравнивать с прочими фолиантами. Краткость - сестра таланта. Избыточности практически нет. Ну или почти нет. Тем не менее, за кажущейся простотой кроется много тонкостей или, по другому, моментов, которые очевидны и просты лишь на первый взгляд. Либо много неочевидных моментов, которые просто незаметны.
Но избыточность все-таки можно надумать. Вырвав с мясом тексты программ, фрагменты текста, которые заменимы соответствующими манами и предложения, которые ну уж совсем просты (опять таки это мой взгляд, но планку "простоты" я постарался опустить как только можно ниже), можно получить короткий, но весьма полезный на мой взгляд, перечень абзацев (предложений, положений, постулатов - тут я очень затрудняюсь с выбором слова), которые очень полезны для просмотра после прочтения этой книги. И могут заменить повторное ее чтение, тем более, не смотря на ее краткость, за день ее не прочитаешь. То есть этот пост - это попытка написать добротный конспект этой книги. Получилось или нет - судить читателю.
Текст будет меняться. Постараюсь придерживаться духа опенсорс. Основная версия этого текста находится тут. Комментарии принимаются в этом блоге. Приветствую любые комментарии.
  • Диапазоны значений int и float зависят от используемой машины.
  • Базовые типы для данных: char - один байт, short, long, double - размеры этих объектов зависят от машины.
  • Из базовых типов можно создать: массивы, структуры, объединения, указатели на объекты базовых типов, функции, возврашающие значения этих типов.
  • printf не является частью языка.
  • #define имя подставляемый_текст
    точка с запятой в конце не ставится.
  • Простейшие из функций для чтения и записи одного символа:
    c=getchar(); putchar(c);
  • В while и for условие проверяется до того как выполняется тело цикла.
  • Символ, заключенный в одинарные кавычки представляет собой целое значение (символьная константа).
  • Под терминами "формальный аргумент" и "фактический аргумент" будут пониматься как параметр и аргумент соответственно.
  • В Си все аргументы функций передаются "по значению".
  • Внешняя переменная должна быть определена, причем только один раз, вне любой функции, в этом случае ей будет выделена память. Она должна быть объявлена во всех функциях, которые ею пользуются. Объявление содержит сведения о типе переменной. Объявление может быть явным (extern) или неявным (нужная информация получается из контекста).
  • Чтобы функция могла пользоваться внешней переменной ее надо объявить extern. Если определение внешней переменной в файле расположено выше функции (где она используется), то объявление extern можно не использовать. Если же определение находится в другом файле, то extern обязательно.
  • Из соображений совместимомти пустой список аргументов выключает проверки на соответствие аргументов. Поэтому, чтобы явно указать отсутствие аргументов вместо someFunction() надо писать someFunction(void).
  • Удобно собрать все объявления внешних переменных и функций в один header и помещать его в начало каждого исходного файла.
  • Определение располагается в месте, где переменная создается и ей отводится память.
  • Объявление - там, где фиксируется природа переменной, но память не отводится.
  • Не начинайте имена переменных с "_", так как многие имена переменных библиотечных програм начинаются с этого знака.
  • Для внутренних имен значимыми являются первые 31 символ. Для имен функций и внешних переменных число значимых символов может быть больше 31, так как эти имена обрабатываются ассемблером и загрузчиком а языком (компиллятором) не контролируются.
  • Уникальность внешних имен гарантируется только в пределах 6 символов.
  • Базовые типы:
    char - 1 байт;
    int, float, double;
    Квалификаторы: short, long, signed, unsigned;
    short int - ("int" можно опустить) 2 байта;
    long int - ("int" можно опустить) 4 байта;
    2 байта <= short <= int <= long >= 4 байта;
  • Возможные комбинации:
    unsigned char;
    unsigned int;
    unsigned short;
    unsigned long;
    signed char;
    signed int;
    signed short;
    signed long;
    float <= double <= long double;
  • Если l, L, u, U, ul или UL - стоят после цифры, то это целые константы.
    Если f, F, l или L - константы с плавающей точкой.
  • '0'=48, '\n' - символьная (целая) константа.
  • 0x 0X - стоит перед цифрами. Это константа в шестнадцатеричном представлении.
    '\000' '\x00' - запись восьмеричного или шестнадцатеричного кодов соответственно.
  • Полный набор эскейпов: \a, \b, \f, \n, \r, \t, \v, \\, \?, \', \", \000, \xhh;
    '\0' тождественно 0 (ноль).
  • Константные выражения вычисляются во время компилляции, а не выполнения.
  • Строковая константа или строковый литерал - это нуль или более символов, заключенных в ".
  • Во внутреннем представлении строки в конце обязательно присутствует '\0'.
  • strlen вычисляет длину без учета '\0'.
  • 'x' - не то же, что "x"!
  • Константы перечисления - это список целых констант.
  • Первое имя в enum имеет значение 0. enum - удобный способ присваивать константам имена, причем значения констант генерируются автоматически. Разрешается объявлять переменные типа enum, но компиллятор не обязан проверять входят ли присваиваемые значения в тип enum. Но возможность такой проверки делает enum лучше #define. Отладчик может печатать значения переменных типа enum в символьном виде.
  • Инициализация неавтоматической переменной осуществляется только один раз - перед тем как программа начнет выполняться. При этом начальное значение должно быть константным выражением. Явно инициализируемая автоматическая переменная получает начальное значение на входе в функцию или блок. Ее начальным значением может быть любое выражение. Без инициализации там будет мусор.
  • Внешние и статические переменные по умолчанию имеют значение 0.
  • Квалификатор: const.
  • Реакция на попытку изменить константу зависит от компиллятора.
  • Оператор % к float и double не применяется.
  • В какую сторону будет усечена дробная часть при выполнении / и каким будет знак результата % с отрицательными числами, зависит от машины.
  • Автоматически производятся лишь те преобразования, которые без потери информации превращают операнды с меньшим диапазоном значений в операнды с большим диапазоном. Выражения, в которых могла бы теряться информация влекут предупреждение, но они допустимы.
  • При преобразовании char в int происходит одно из двух: либо старший бит char "распространяется" либо слева добавляются только нули. Что произойдет - зависит от машины.
  • Гарантируется, что любой символ из стандартного набора печатных символов никогда не будет отрицательным числом. Произвольный char на одних машинах может быть отрицательным, на других - положительным. Для совместимости переменные char, в которых хранятся несимвольные данные, следует специфицировать явно как signed или unsigned.
  • Операторы сравнения (например <) и логические выражения, перемежаемые операторами && и ||, определяют выражение-условие, которое имеет значение 1, когда оно истинно, и 0, когда оно ложно. Однако функции, подобные isdigit, в качестве истины могут выдавать любое ненулевое значение. В общем случае, если бинарный оператор имеет разнотипные операнды, прежде чем он будет выполняться, "низший" тип будет повышаться до "высшего". Результат будет иметь высший тип. Операторы типа float не приводяться автоматически к типу double. Пусть значение типа int - 16 битов, а типа long - 32 бита. Тогда: unsigned int повышается до signed long поэтому -1L<1u>1UL.
  • При присваивании значение правой части приводиться к типу левой части, который и является типом результата.
  • Тип char превращается в int путем распространения знака или другим (?) способом.
  • Тип long int преобразуется в short int или в значение типа char путем отбрасывания старших разрядов.
  • Тип float переводится в тип int отбрасыванием дробной части.
  • Если double переводится во float, то значение либо округляется, либо обрезается. Это зависит от реализации.
  • Так как аргумент в вызове функции есть выражение, при передаче его функции также возможно преобразование типа. При отсутствии прототипа функции аргументы типа char и short переводятся в int, а float - в double. Вот почему в примерах этой книги объявлялись аргументы типа int или double, даже тогда, когда в вызове функции использовались аргументы типа char или float.
  • Заметим, что операция приведения всего лишь вырабатывает значение указанного типа, но саму переменную не затрагивает.
  • n = n & 0177; // обнуляет все разряды, кроме младших семи.
  • Следует отличать побитовые и логические операторы.
  • Операторы << и >> сдвигают влево или вправо левый операнд на число битовых позиций, задаваемое правым операндом, который должен быть неотрицательным. Сдвиг влево (<<) эквивалентен умножению на соответствующую степень двойки. Сдвиг вправо беззнаковой величины всегда сопровождается заполнением освобождающихся разрядов нулями. Сдвиг вправо знаковой величины на одних машинах происходит с распространением знака (арифметический сдвиг), на других - с заполнением освобождающихся разрядов нулями (логический сдвиг). Таким образом, объявление операнда правого сдвига unsigned, гарантирует, что освобождающиеся биты будут заполняться нулями независимо от машины. n = n & ~077; // обнуляет последние шесть разрядов. Заметим, что форма записи ~077 является машинно независимой.
  • Типом и значением любого выражения присваивания являются тип и значение его левого операнда после завершения присваивания.
  • Если второе и третье выражения тернарного опреатора ?: принадлежат разным типам, то тип результата определяется соответствующими правилами преобразования вне зависимости от того какое выражение вычисляется - второе или третье.
  • Си подобно многим языкам не фиксирует очередность вычисления операндов оператора (за исключением "&&", "||", "?:" и ","). Очередность вычисления аргументов функции также не определена. Обращения к функциям, вложенные присваивания, инкрементные и декрементные операторы дают "побочный эффект", проявляющийся в том, что при вычислении выражения значения некоторых переменных изменяются. Лучший порядок вычислений определяется архитектурой машины.
  • Выражение становиться инструкцией, если в конце его поставить точку с запятой. Фигурные скобки используются для объединения объявлений и инструкций в составную инструкцию или блок. С точки зрения синтаксиса эта конструкция воспринимается как одна инструкция. Переменные могут быть объявлены внутри любого блока. После закрывающей скобки в конце блока точка с запятой не ставится.
  • Запись if(выражение) и if(выражение != 0 ) эквивалентны.
  • Отсутствие else-части в одной из вложенных друг в друга if-конструкций приводит к неоднозначному толкованию. else связывают с ближайшим if, у которого нет своего else. Если нужна другая интерпретация, то пользуются фигурными скобками. Совет: вложенные if всегда обрамляйте фигурными скобками.
    Пример:
    if(выражение)

    for()
    if(выражение){
    блок выражений
    }
    else
    выражение;

    else относится к if в теле цикла! Хотя, на первый взгляд к внешнему if.
  • break и return - наиболее распространенные средства выхода из переключателя.
  • Совет (при пользовании переключателем switch): даже в конце последней ветви помещайте инструкцию break, хотя с точки зрения логики в ней нет никакой необходимости.
  • Инструкция:
    for(выражение1;выражение2;выражение3)
    инструкция
    эквивалентна конструкции:
    выражение1;
    while(выражение2){
    инструкция
    выражение3;
    }

    если не считать отличий в поведении инструкции continue. При отсутствии выражение1 или выражение3 считается, что их просто нет в конструкции цикла. При отсутствии выражение2 предполагается, что его значение как бы всегда истинно.
  • Отличие цикла for в си от от DO-цикла в Фортране (и for-цикла в Паскале) в том, что значенте идекса цикла и его предельного значения могут изменяться внутри цикла и значение индекса после выхода из цикла всегда определено.
  • Пара выражений, разделенных запятой, вычисляется слева направо. Типом и значением результата является тип и значение результата правого выражения. Запятые, разделяющие аргументы функции, переменные в объявлениях и пр. (?) не являются операторами-запятыми и не обеспечивают вычисление слева направо. Запятые-операторы более всего уместны в конструкциях, которые тесно связаны друг с другом, а также в макросах, в которых многоступенчатые вычисления должны быть выражены одним выражением.
  • Инструкция break вызывает немедленный выход из самого внутреннего из объемлющих ее циклов или переключателей (for, while, do-while, switch). Инструкция continue вынуждает ближайший объемлющий цикл (for, while, do-while) начать следующий шаг итерации (немедленный переход к проверке условия для while и do-while и для for - выполнение выражение3). Внутри переключателя switch, расположенного в цикле, инструкция continue вызовет переход к следующей итерации этого цикла. То есть она применяется к циклам, но не к переключателю.
  • Определение любой функции имеет следующий вид:
    тип-результата имя-функции(объявления аргументов) { объявления и инструкции; }
    Если тип результата опущен, то предполагается, что функция возвращает значение типа int.
  • Любая программа - это просто совокупность определений переменных и функций. Связи между функциями осуществляются через аргументы, возвращаемые значения и внешние переменные. В исходном файле функции могут располагаться в любом порядке. Исходную программу можно разбивать на любое число файлов, но так, чтобы ни одна из функций не оказалась разрезанной.
  • За словом return может следовать любое выражение. Если потребуется, то выражение будет приведено к возвращаемому типу функции.
    return выражение;
    Выражение можно заключить в скобки, но они необязательны.
  • Вызывающая фукция вправе проигнорировать возвращаемое значение. Выражение в return может отсутствовать. Также управление возвращается в вызывающую функцию без результирующего значения также и в том случае, когда вычисления достигли конца (последней закрывающей фигурной скобки функции).
  • Объявление и определение функции должны соответствовать друг другу. Если в одном исходном файле сама функция и обращение к ней не соответствуют друг другу, то компилятор сгенерирует ошибку. Но если вызываемая функция была скомпилируема отдельно, то несоответствие не будет обнаружено, что приведет на этапе выполнения к бессмысленному результату. (Разговор идет только про возвращаемые значения?) Одной из причин подобного несоответствия может быть ситуация, когда отсутствует прототип вызываемой функции и тогда функция неявно объявляется при первом своем появлении в каком-нибудь выражении.
  • Если в выражении встретилось имя, нигде ранее не объявленное, за которым следует открывающаяся скобка, то такое имя по контексту считается именем функции, возвращающей результат целого типа. При этом относительно аргументов ничего не предполагается.
  • Если в объявлении функции аргументы не указываются, то и в этом случае считается, что об аргументах ничего не известно, и все проверки на соответствие параметров будут выключены. Это сделано для совместимости. Но этой особенностью лучше не пользоваться. Если надо явно указать отсутствие аргументов у функции, то используется слово void.
  • При преобразовании типов возможна потеря информации, и некоторые компиляторы предупреждают об этом. Оператор приведения явно указывает на необходимость преобразования типа и подавляет любое предупреждающее сообщение.
  • Сами функции всегда являются внешними объектами, поскольку в Си запрещено определять функции внутри других функций. По умолчанию одинаковые внешние имена, используемые в разных файлах, относятся к одному и тому же внешнему объекту (функции). В стандарте это называется external linkage. В этом смысле внешние переменные похожи на области COMMON в Фортране.
  • В выражении func()-func(); очередность обращения к func() не определена, поэтому, учитывая некоммутативность оператора "-", результат может быть неверен.
  • Параметры функции фактически являются локальными переменными.
  • Если на внешнюю переменную нужно сослаться до того, как она определена, или если она определена в другом файле, то ее объявление должно быть помечено словом extern.
  • Объявление объявляет свойства переменной (прежде всего ее тип), а определение, кроме того, приводит к выделению для нее памяти.
  • На всю сосвокупность файлов, из которых состоит исходная программа, для каждой внешней переменной должно быть одно-единственное определение. Другие файлы, чтобы получить доступ к внешней переменной, должны иметь в себе объявление extern.
  • В определениях массивов необходимо указывать их размеры, что в объявлениях extern не обязательно.
  • Инициализировать внешнюю переменную можно только в определении.
  • Указание static, примененное к внешней переменной или функции, ограничивает область видимости соответствующего объекта концом файла.
  • Как и автоматические, внутренние статические переменные локальны в функциях, но отличаются от первых тем, что существуют постоянно.
  • Объявление register сообщает компилятору, что данная переменная будет интенсивно использоваться. Компилятор может проигнорировать это указание. Объявление register может применяться только к автоматическим переменным и к формальным параметрам функции. Располагаться в регистрах может лишь небольшое число переменных, причем только определенных типов. Применительно к регистровой переменной независимо от того, выделен на самом деле для нее регистр или нет, не определено понятие адреса.
  • Объявления переменных (вместе с инициализацией) разрешено помещать не только в начале функции, но и после любой левой фигурной скобки.
  • Переменные static инициализируются только одиин раз при первом входе в блок.
  • При отсутствии явной инициализации для внешних и статических переменных гарантируется их обнуление. Автоматические и регистровые переменные имеют неопределенные начальные значения ("мусор").
  • Для внешних и статических переменных инициализирующие выражения должны быть константными, при этом инициализация осуществляется только один раз до наяала выполнения программы. Инициализация автоматических и регистровых переменных выполняется каждый раз при входе в функцию или блок. Для таких переменных инициализирующее выражением может служить любое выражение, использующее ранее определенные значения, включая даже и вызовы функций. В сущности, инициализация автоматической переменной - это более короткая (?) запись инструкции присваивания.
  • Если размер массива не указан, то длину массива компилятор вычисляет по числу заданных инициализаторов. Если количество инициализаторов меньше числа, указанного в определении длины массива, то для внешних, статических и автоматических (а какие ж еще бывают?) переменных оставшиеся элементы будут нулевыми.
  • char pattern[]="ould"; char pattern[]={'o','u','l','d','\0'}; Первая запись является более коротким вариантом вторной.
  • #define заменяет одни текстовые последовательности на другие.
  • Включения (делаемые директивой #include ?) необязательно являются файлами. Технические детали того, как осуществляется доступ к заголовкам, зависят от реализации.
  • Область видимости имени, определенного в #define, простирается от данного определения до конца файла. Подстановка (#define) осуществляется только для тех имен, которые расположены вне текстов, заключенных в кавычки.
  • #define dprint(expr) printf(#expr "=%g\n", expr)
    Внутри фактического аргумента каждый знак " заменяется на \" ну а каждая \ на \\.
  • Оператор ## позволяет в макрорасширениях конкатенировать аргументы. Правила вложенных использований этого оператора не определены.
  • Константное целое выражение, заданное в строке #if, не должно содержать ни одногооператора sizeof или приведения к типу и ни одной enum-константы.
  • #if defined(var) эквивалентно #ifdef var
    #if !defined(var) эквивалентно #ifndef var
  • В качестве обобщенного указателя предлагается тип void* (до введения стандарта ANSI эту роль выполнял тип char*).
  • Применительно к любой машине верны следующие утверждения:
    1) один байт может хранить значение типа char;
    2) два байта - short;
    3)четыре байта - long;
  • Указатель - это, как правило, два или четыре байта.
  • Унарный оператор * есть оператор косвенного доступа.
  • Синтаксис объявления переменной и функции "подстраивается" под синтаксис выражений, в которых эта переменная может встретиться.
  • По определению значение переменной или выражения типа массив есть адрес нулевого элемента массива. Следующие записи эквивалентны:
    pa=&a[0];
    pa=a;

    Также эквивалентны такие записи:
    pa[i]
    *(pa+i)

    так как вычисляя первую запись, Си преобразует ее во вторую.
    И такие:
    &a[i]
    a+i
  • Между ипменем массива и указателем, выступающим в роли имени массива, есть одно различие. Указатель - это переменная, поэтому можно написать pa=a или pa++. Но имя массива не является переменной и записи вроде a=pa и a++ не допускаются.
  • Если имя массива передается функции, то последняя получает в качестве аргумента адрес его начального элемента. Внутри вызываемой функции этот аргумент является локальной переменной, содержащей адрес. Формальные параметры char s[]; и char *s; в определении функции эквивалентны. Если функции в качестве аргумента передается имя массива, то она может рассматривать так, как ей удобно - либо как имя массива, либо как указатель, и поступать с ним соответственно. Она даже может использовать оба вида записи, если это покажется уместным и понятным.
    Функции можно передавать часть массива. Если есть уверенность, что элементы массива существуют, то возможно индексирование и в "обратную" сторону по отношению к нулевому элементу. Нельзя "выходить" за границы массива и тем самым обращаться к несуществующим объектам.
  • Если p есть указатель на некоторый элемент массива, то p++ увеличивает p так, чтобы он указывал на следующий, а p+=i - на i-й элемент после того, на который указывал ранее.
  • С помощью malloc можно работать с массивом у которого нет имени.
  • Указатели и целые не являются взаимозаменяемыми объектами. Константа нуль - единственное исключение из этого правила: ее можно присвоить указателю, и указатель можно сравнивать с нулевой константой. Вместо цифры нуль можно писать NULL (определена в ).
  • Если указатели указывают на элементы одного и того же массива, то к ним можно применять операторы отношения и их можно вычитать один из другого. Если элементы не из одного массива, то результат не определен. Исключение: можно использовать адрес несуществующего "следующего за массивом" элемента.
  • Указатели и целые можно складывать и вычитать. Допускается также вычитание указателей, если они указывают на элементы из одного массива. Разрешено присваивание одного указателя другому того же типа. Других операций с указателем производить не допускается.
  • Указателю одного типа нельзя присвоить указатель другого типа, не выполнив предварительно операции приведения (исключение - указатели *void).
  • Число символов в строке может быть слишком большим, чтобы хранить его в переменной типа int. Тип ptrdiff_t достаточный для хранения разности (со знаком) двух указателей, определен в . Имеется также тип беззнакового целого size_t, возращаемого оператором sizeof.
  • Существует различие между следующими определениями:
    char amessage[] = "some string";
    char *pmessage = "some string";

    Первое - это массив с последним элементом '\0'. Отдельные символы внутри массива могут изменяться, но amessage указывает всегда на одно и то же место в памяти. pmessage - указатель на строковую константу. Сам он может изменяться. Если попытаться изменить содержимое константы, то результат неопределен.
  • *p++ = val; /*поместить val в стек*/
    val = *--p; /*извлечь из стека и поместить в val*/
  • Если двумерный массив передается некоторой функции, то ее можно определить несколькими путями:
    f(int massive[n][m]){...}
    f(int massive[][m]){...}
    f(int (*massive)[m]){...}

    В общем случае только первое измерение можно не задавать, все другие специфицировать обязательно.
  • Рассмотрим два определения:
    int a[m][n];
    int *b[n];

    а является истинным массивом - для всех его элементов будет выделена память, а вычисление смещения элемента будет вестись по формуле n*строка+столбец. Для b же определено только n указателей, причем без инициализации. Инициализация должна задаваться явно, либо статически, либо в программе. Важное преимущество массива указателей в том, что строки такого массива могут иметь разные длины. Указатель из такого массива может вообще ни на что не указывать.
  • По соглашению argv[0] есть имя вызываемой программы, так что значение argc никогда не бывает меньше единицы. Стандарт требует, чтобы argcargv[argc] всегда был пустым указателем. argv - указатель на массив указателей, поэтому мы можем работать с ним как с указателем, а не как с индексируемым массивом. Заметим, что *++argv является указателем на аргумент-строку, а (*++argv)[0] - его первым символом, на который можно сослаться и другиим способом: **++argv.
  • За словом struct может следовать имя, называемое тегом структуры. Тэг дает название структуры данного вида и далее может служить кратким обозначением той части объявления, которая заключена в фигурные скобки. Перечисленные в структуре переменные называются элементами (членами) структуры. Единственно возможный опреации над структурами - это их копирование, присваивание, взятие адреса с помощью &, и осуществление доступа к ее элементам. Копирование и присваивание включают в себя предачу функциям аргументов и возврат ими значений. Структуры нельзя сравнивать.
  • Число элементов массива struct можно вычислить по формуле:
    someType someMassive[];
    размер someMassive / размер stuct someType
  • Выражения sizeof объект и sizeof (имя типа) выдают целые значения, равные размеру указанного объекта или типа в байтах. (строго говоря, sizeof выдает беззнаковое целое, тип которого size_t определен в )
  • Препроцессор не обращает внимания на имена типов, поэтому оператор sizeof нельзя применять в #if. В #define выражение препроцессором не вычисляется, поэтому можно использовать оператор sizeof в #define.
  • Не обязательно и возможно опасно, если malloc или ее заместитель не может быть объявлен как функция, возращающая void*. Явное приведение типа может скрыть случайную ошибку.
  • Объявляемый в typedef тип стоит на месте имени переменной в обычном объявлении, а не сразу за словом typedef. С точки зрения синтаксиса слово typedef напоминает класс памяти - extern, static и т.д.
  • Следует подчеркнуть, что объявление typedef не создает объявления нового типа, а сообщает новое имя уже существующему типу. Нового смысла эти имена не несут, они объявляют переменные в точности с теми же свойствами, как если бы те были объявлены напрямую без переименования типа. Фактически typedef аналогичен #define c тем лишь отличием, что при интерпретации компилятором он может справиться с такой текстовой подстановкой, которая не может быть обработана препроцессором.
  • Для применения typedef существуют две причины:
    1)Параметризация программы. Если с помощью typedef объявить типы данных, которые, возможно, являются машинно-зависимыми, то при переносе программы на другую машину потребуется внести изменения только в определения typedef.
    2)Желание сделать более ясным текст программы.
  • Объединения позволяют хранить разнородные данные в одной и той же области памяти без включения в программу машинно-зависимой информации. Цель введения в программу объединения - иметь переменную, которая бы на законных основаниях хранила в себе значения нескольких типов. В том случае, если нечто запомнено как значение одного типа, а извлекается как значение другого, результат зависит от реализации. Фактически объединение - это структура, все элементы которой имеют нулевое смещение относительно ее базового адреса и размер которой позволяет поместиться в ней самому большому ее элементу, а выравнивание этой структуры удовлетворяет всем типам объединения.Операции, применимые к структурам, годятся и для объединений (присваивание, копирование, взятие адреса, доступ к отдельным элементам).
  • Инициализировать объединение можно только значением, имеющим тип его первого элемента.
  • Битовое поле - некоторое множество битов, лежащих рядом внутри одной, зависящей от реализации единицы памяти, называемую здесь "словом". Синтаксис определения полей и доступа к ним базируется на синтаксисе структур. Почти все технические детали, касающиеся полей, в частности, возможность поля перейти границу слова, зависят от реализации. Поля могут не иметь имени, с помощью такого поля организуется пропуск нужного количества бит. Ширина, равная нулю, используется, когда требуется выйти на границу следующего слова. На одних машинах поля размещаются слева направо, на других - наоборот. Поля можно определять только с типом int, а для того чтобы обеспечить переносимость, надо явно указывать signed или unsigned. Поля не могут быть массивами и не имеют адресов, т.е. оператор & к ним не применим.
  • Простейший механизм ввода - чтение одного символа из стандартного ввода функцией int getchar(void)
  • EOF обычно равно -1, но, чтобы не зависеть от конкретного ее значения, обращаться к ней следует по имени.
  • prog предписывает программе prog считывать символы из infile. Причем переключение ввода делается так, что сама программа не замечает подмены. В частности строка "" не будет включена в аргументы командной строки argv.
  • FILE - это имя типа, наподобие int, а не тег структуры. Оно определено с помощью typedef.
  • Полный набор "системных" типов находится в <sys/types.h>.
  • Для каждой из машин существует тип, предъявляющий самые большие требования на выравнивание, и, если по некоему адресу допускается размещение объекта этого типа, то по нему можно разместить и объекты всех других типов.