Мова C в питаннях і відповідях. Частина 3 (FAQ), Різне, Програмні керівництва, статті

5.5 Не розумію, чому не можна використовувати незмінні значення при
ініціалізації змінних і завданні розмірів масивів, як у наступному
прикладі:

const int n = 5;
int a[n];

В: кваліфікатор const означає “тільки для читання”. Будь-який об’єкт

кваліфікований як const, являє собою нормальний об’єкт,
існуючий під час виконання програми, якому не можна присвоїти інше
значення. Отже, значення такого об’єкта – це _не_
константне вираз в повному сенсі цього слова. (У цьому сенсі З
не схожий на С + +). Якщо є необхідність в істинних константах,
працюють під час компіляції, використовуйте препроцесорну директиву
        #define.

Дивись: ANSI Розд. 3.4.

5.6 Яка різниця між “char const * p” і “char * const p”?

О: “char const * p” – це покажчик на постійну літеру (її не можна
змінити); “char * const p” – це незмінний покажчик на
змінну (її можна міняти) типу char. Зарубайте це собі на носі.
Див також 10.4.

Дивись: ANSI Розд. 3.5.4.1.

5.7 Чому не можна передати char ** функції, яка чекає const char **?

В: Можна використовувати покажчик-на-Т будь-яких типів Т, коли очікується
покажчик-на-const-Т, але правило (точно певний виключення з
нього), що дозволяє незначні відмінності в _указателях_, не може
застосовуватися рекурсивно, а тільки на самому верхньому рівні.

Необхідно використовувати точне приведення типів (тобто в даному випадку
(Const char **)) при присвоєнні або передачі покажчиків, які
мають відмінності на рівні непрямої адресації, відмінному від першого.

Дивись: ANSI Розд. 3.1.2.6 c. 26, Розд. 3.3.16.1 c. 54,
Розд. 3.5.3 c. 65.

5.8 Мій ANSI компілятор відзначає розбіжність, коли зустрічається з
деклараціями

       extern int func(float);

int func(x)
float x;
{…

В: Ви змішали декларацію в новому стилі “extern int func (float);”
з визначенням функції в старому стилі “int func (x) float x;”.
Змішання стилів, як правило, безпечно (див. питання 5.9), але
тільки не в цьому випадку. Старий С (і ANSI С при відсутності
прототипів і в списках аргументів змінної довжини) “розширює”
аргументи певних типів при передачі їх функцій. Аргументи
типу float перетворюються в тип double, літери і короткі цілі
перетворюються в тип int. (Якщо функція визначена в старому стилі,
параметри автоматично перетворюються в тілі функції до менш ємним,
якщо таке їх опис там.).

Це утруднення може бути подолано або за допомогою визначень
в новому стилі,

int func(float x) { … }

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

extern int func(double);

(В цьому випадку для більшої ясності було б бажано змінити і
визначення в старому стилі так, щоб параметр, якщо тільки не
використовується його адресу, був типу double).

Можливо, буде безпечніше уникати типів char, short int, float для
повертаються значень і аргументів функцій.

Дивись: ANSI Розд. 3.3.2.2.

5.9 Чи можна змішувати визначення функцій в старому та новому стилі?

В: Змішання стилів абсолютно законно, якщо дотримується обережності
(Зверніть особливу увагу на питання 5.8). Зауважте, однак, що
визначення функцій в старому стилі вважається виходять з
вживання, і в один прекрасний момент підтримка старого стилю
може бути припинена.

5.10 Чому оголошення

extern f(struct x {int s;} *p);

породжує невиразне попередження “struct x introduced in
prototype scope “? (структура оголошена в зоні видимості прототипу)?

В: У дивному протиріччі із звичайними правилами для областей видимості
структура, оголошена тільки в прототипі, не може бути сумісна з
іншими структурами, оголошеними в цьому ж файлі. Більш того,
всупереч очікуванням тег структури не може бути використаний після
такого оголошення (зона видимості об’вленія простягається до кінця
прототипу). Для вирішення проблеми необхідно, щоб прототипу
передувало “порожнє” оголошення

struct x;

, Яке зарезервує місце в області видимості файлу для
визначення структури x. Визначення буде завершено оголошенням
структури всередині прототипу.

Дивись: ANSI Розд. 3.1.2.1 c. 21, Розд. 3.1.2.6 c. 26,
Розд. 3.5.2.3 c. 63.

5.11 У мене виникають дивні повідомлення про помилки усередині коду,
“Вимкненого” за допомогою # ifdef.

В: Згідно ANSI C, текст, “вимкнений” за допомогою # if, # ifdef, або
# Ifndef повинен складатися з “коректних одиниць препроцессірованія”.
Це означає, що не повинно бути незакритих коментарів або
лапок (зверніть особливу увагу, що апостроф всередині скорочено
записаного слова виглядає як початок літерної константи).
Всередині лапок не повинно бути символів нового рядка. Отже,
коментарі і псевдокод завжди повинні знаходитися між
безпосередньо призначеними для цього символами початку і кінця
коментаря / * і * /. (Дивіться, однак, питання 17.14 і 6.7).

Дивись: ANSI Розд. 2.1.1.2 c. 6, Розд. 3.1 c. 19 рядок 37.

5.12 Можу я оголосити main як void, щоб припинилися дратівливі
повідомлення “main return no value”? (Я викликаю exit (), так що
main нічого не повертає).

В: Ні. main повинна бути оголошена як повертає int і використовує
або два, або жодного аргументу (відповідного типу). Якщо
використовується exit (), але попереджувальні повідомлення не зникають,
Вам потрібно буде вставити зайвий return, або використовувати, якщо
це можливо, директиви на кшталт “notreached”.

Оголошення функції як void просто не впливає на попередження
компілятора; крім того, це може породити іншу послідовність
виклику / повернення, несумісну з тим, що чекає викликає функція
(У разі main це виконуюча система мови С).

Дивись: ANSI Розд. 2.1.2.2.1 c. 7-8.

5.13: У точності чи еквівалентний повернення статусу за допомогою exit (status)
поверненню за допомогою return?

В: Формально, так, хоча невідповідність виникають в деяких старих
нестандартних системах, в тих випадках, коли дані, локальні
для main (), можуть знадобитися в процесі завершення виконання
(Може бути при викликах setbuf () або atexit ()), або при рекурсивному
виклик main ().

Дивись: ANSI Розд. 2.1.2.2.3 c. 8.

5.14 Чому стандарт ANSI гарантує лише шість значущих символів (при
відсутності відмінності між великими та малими символами) для
зовнішніх ідентифікаторів?

В: Проблема в старих компонувальника, які не залежать ні від стандарту
ANSI, ні від розробників компіляторів. Обмеження полягає в тому,
що тільки перші шість символів _значіми_, а не в тому, що довжина
ідентифікатора обмежена шістьма символами. Це обмеження
дратує, але його не можна вважати нестерпним. У Стандарті воно
позначено як “виходить з ужитку”, так що в наступних
редакціях воно, ймовірно, буде ослаблено.

Цю поступку сучасним компонувальника, що обмежує кількість
значущих символів, обов’язково потрібно робити, не звертаючи уваги
на бурхливі протести деяких програмістів. (В “Коментарях”
сказано, що збереження цього обмеження було “найбільш болючим”.
Якщо Ви не згодні або сподіваєтеся за допомогою якогось трюка змусити
компілятор, обтяжений обмежує кількість значущих символів
компонувальником, розуміти більшу кількість цих символів, читайте
чудово написаний розділ 3.1.2 X3.159 “Коментарів” (див. питання
5.1), де обговорюється декілька такого роду підходів і пояснюється,
чому ці підходи не можуть бути узаконені.

Дивись: ANSI Розд. 3.1.2 c. 21, Розд. 3.9.1 c. 96, Rationale
Розд. 3.1.2 c. 19-21.

5.15 Яка різниця між memcpy і memmove?

В: memmove гарантує правильність операції копіювання, якщо дві
області пам’яті перекриваються. memcpy не дає такої гарантії і,
отже, може бути більш ефективно реалізована. У випадку
сумнівів краще застосовувати memmove.

Дивись: ANSI Розд. 4.11.2.1, 4.11.2.2, Rationale Разд.4.11.2.

5.16 Мій компілятор не транслює найпростіші тестові програми, видаючи
всілякі повідомлення про помилки.

В: Мабуть, Ваш компілятор розроблений до прийому стандарту ANSI і
тому не здатний обробляти прототипи функцій тощо.
Див також питання 5.17 і 17.2.

5.17 Чому не визначені деякі підпрограми із стандартної ANSI-
бібліотеки, хоча в мене ANSI сумісний компілятор?

В: Немає нічого незвичайного в тому, що компілятор, що сприймає ANSI
синтаксис, не має ANSI-сумісних головних файлів або стандартних
бібліотек. Див також питання 5.16 і 17.2.

5.18 Чому компілятор “Frobozz Magic C”, про який йдеться, що він
ANSI-сумісний, не транслює мою програму? Я знаю, що текст
підпорядковується стандарту ANSI, тому що він транслюється компілятором
gcc.

В: Практично всі компілятори (а gcc – більше за інших) підтримують
деякі нестандартні розширення. Чи впевнені Ви, що знехтуваний
текст не застосовує одне з таких розширень? Небезпечно експериментувати
з компілятором для дослідження мови. Стандарт може допускати
відхилення, а компілятор – працювати невірно. Див також питання 4.4.

5.19 Чому мені не вдаються арифметичні операції з покажчиком типу
void * ?

В: Бо компілятору не відомий розмір об’єкта, на який
вказує void *. Перед арифметичними операціями використовуйте
оператор приведення до типу (char *) або до того типу, з яким
збираєтеся працювати. (Дивіться, однак, питання 2.18).

5.20 Чи правильна запис a [3] = “abc”? Що це означає?

В: Цей запис вірна в ANSI C (і, можливо, в деяких більш ранніх
компіляторах), хоча корисність такого запису сумнівна. Оголошується
масив розміру три, ініціалізіруемих трьома буквами “a”, “b”, і “c” без
завершального стринг символу “
використовуватися як стринг функціями strcpy, printf% s і т.п.

Дивись: ANSI Розд. 3.5.7 c. 72-3.

5.21 Що таке # pragma і де це може стати в нагоді?

В: Директива # pragma забезпечує особливу, точно певну “лазівку”
для виконання залежать від реалізації дій: контроль за
лістингом, упаковку структур, придушення попереджувальних повідомлень
(На кшталт коментарів / * NOTREACHED * / старої програми lint) і т.п.

Дивись: ANSI Розд. 3.8.6.

5.22 Що означає “# pragma once”? Я знайшов цю директиву в одному з
головних файлів.

В: Це розширення, реалізоване в деяких препроцесора, робить
головний файл Ідемпотентний, тобто ефект від одноразового включення
файлу дорівнює ефекту від багаторазового включення. Ця директива
призводить до того ж результату, що і прийом з використанням # ifndef,
описаний в питанні 6.4.

5.23 Начебто існує відмінність між залежним від реалізації,
неописаним (unspecified) і невизначеним (undefined) поведінкою.
У чому ця різниця?

В: Якщо говорити коротко, то при залежному від реалізації поведінці
необхідно вибрати один варіант і документувати його. При
невимовному поведінці також вибирається один із варіантів, але в цьому
випадку немає необхідності це документувати. Невизначений
поведінка означає, що може статися все що завгодно. У жодному з
цих випадків Стандарт не висуває вимог; в перших двох випадках
Стандарт іноді пропонує (а може і вимагати) вибір з кількох
близьких варіантів поведінки.
Якщо Ви зацікавлені в написанні мобільних програм, можете
ігнорувати відмінності між цими трьома випадками, оскільки всіх їх
необхідно буде уникати.

Дивись: ANSI Разд.1.6, особливо “Rationale”.


6. Препроцесор С.

6.1 Як написати макрос для обміну будь-яких двох значень?

В: На це питання немає хорошого відповіді. При обміні цілих значень може
бути використаний добре відомий трюк з використанням виключає
АБО, але це не спрацює для чисел з плаваючою крапкою або покажчиків.
Не годиться цей прийом і у випадку, коли обидва числа – насправді
одне і те ж число. Через багатьох побічних ефектів (див. питання 4.1 і
4.2) не годиться і “очевидне” суперкомпактні рішення для цілих чисел
a ^ = b ^ = a ^ = b. Коли макрос призначений для змінних довільного
типу (зазвичай так і буває), не можна використовувати тимчасову змінну,
оскільки не відомий її тип, а стандартний С не має оператора
typeof.

Якщо Ви не хочете передавати тип змінної третім параметром, то,
можливо, найбільш гнучким, універсальним рішенням буде відмова від
використання макросу.

6.2 У мене є стара програма, яка намагається конструювати
ідентифікатори за допомогою макросу

                        #define Paste(a, b) a/**/b

але в мене це не працює.

В: Те, що коментар повністю зникає, і, отже, може бути
використаний для склеювання сусідніх лексем (зокрема, для створення
нових ідентифікаторів), було недокументованою особливістю
деяких ранніх реалізацій препроцесора, серед яких помітна
була реалізація Рейзера (Reiser). Стандарт ANSI, як і K & R,
стверджує, що коментарі замінюються одиничними пробілами. Але
оскільки необхідність склеювання лексем стала очевидною, стандарт
ANSI ввів для цього спеціальний оператор # #, який може бути
використаний так:

  #define Paste(a, b) a##b

Дивіться також питання 5.4.

Дивись: ANSI Розд. 3.8.3.3 c. 91, Rationale c. 66-7.

6.3 Як найкращим чином написати cpp макрос, в якому є кілька
інструкцій?

В: Звичайно мета полягає в тому, щоб написати макрос, який не відрізнявся
б з вигляду від функції. Це означає, що завершальна крапка з комою
ставиться тим, хто викликає макрос, а в самому тілі макросу її немає.
Тіло макросу не може бути просто складовою інструкцією, укладеної
у фігурні дужки, оскільки виникнуть повідомлення про помилку
(Очевидно, через зайву крапки з комою, що стоїть після інструкції) в
тому випадку, коли макрос викликається після if, а в інструкції if / else
є else-частину.

Зазвичай ця проблема вирішується за допомогою такого визначення:

  #define Func() do {
/ * Оголошення * /
що-ТО1;
що-ТО2;
  /* … */
} While (0) / * (немає завершальній;) * /

Коли при виклику макросу додається крапка з комою, це розширення
стає простою інструкцією незалежно від контексту.
(Оптимізуючий компілятор видалить зайві перевірки або
переходи по умові 0, хоча lint це може і не прийняти.)

Якщо потрібно макрос, в якому немає декларацій або розгалужень, а
всі інструкції – прості вирази, то можливий інший підхід, коли
пишеться одне, укладену в круглі дужки вираз, що використовує
одну чи кілька ком. (Див. приклад в питанні 6.10. Такий підхід
дозволяє також реалізувати “повернення” значення).

Дивись: CT & P Разд.6.3 c. 82-3.

6.4 Чи можна в головний файл за допомогою # include включити інший
головний файл?

В: Це питання стилю, і тут виникають великі суперечки. Багато хто вважає,
що “вкладених за допомогою # include файлів” слід уникати:
авторитетний Indian Hill Style Guide (див. питання 14.3) несхвально
висловлюється про такий стилі; стає важче знайти відповідне
визначення; вкладені # include можуть призвести до повідомлень про
багаторазовому оголошенні, якщо головний файл включений двічі; також
утруднюється коригування керуючого файлу для утиліти Make. З
іншого боку, стає можливим використовувати модульний принцип
при створенні головних файлів (головний файл включає за допомогою
# Include те, що необхідно тільки йому, інакше доведеться
кожен раз використовувати додатковий # include, що здатне викликати
постійну головний біль); За допомогою утиліт, подібних grep (або файлу
tags) можна легко знайти потрібні визначення незалежно від того,
де вони знаходяться, нарешті, популярний прийом:

  #ifndef HEADERUSED
  #define HEADERUSED
… Вміст головного файлу …
  #endif

робить головний файл “Ідемпотентний”, то є такий файл можна
безболісно включати кілька разів; засоби автоматичної
підтримки файлів для утиліти Make (без яких все одно не обійтися
у разі великих проектів) легко виявляють залежності при наявності
вкладених # include. Див також розділ 14.

6.5 Чи працює оператор sizeof при використанні кошти препроцесора
#if?

В: Ні. Препроцесор працює на ранній стадії компіляції, до того як
стають відомі типи змінних. Спробуйте використовувати
константи, визначені у файлі , передбаченому ANSI,
або “сконфигурировать” замість цього командний файл. (А ще краще
написати програму, яка за самою своєю природою нечутлива до
розмірами змінних).

Дивись: ANSI Розд. 2.1.1.2 c. 6-7, Розд. 3.8.1 c. 87
примітка 83.

6.6 Чи можна з допомогою # if дізнатися, як організована пам’ять машини –
за принципом: молодший байт-менший адресу або навпаки?

В: Мабуть, цього зробити не можна. (Препроцесор використовує для
внутрішніх потреб тільки довгі цілі і не має поняття про адресації).
А чи впевнені Ви, що потрібно точно знати тип організації пам’яті?
Вже краще написати програму, яка від цього не залежить.

6.7 Під час компіляції мені необхідно складне препроцесссірованіе, і я
ніяк не можу придумати, як це зробити за допомогою cpp.

В: cpp не задуманий як універсальний препроцесор. Чим змушувати cpp
робити щось йому не властиве, подумайте про написання невеликого
спеціалізованого препроцесора. Легко роздобути утиліту типу
make (1), яка автоматизує цей процес.

Якщо Ви намагаєтеся препроцессіровать щось відмінне від С,
скористайтеся універсальним препроцесором, (таким як m4).

6.8 Мені попалася програма, в якій, на мій погляд, занадто багато
директив препроцесора # ifdef. Як обробити текст, щоб залишити
тільки один варіант умовної компіляції, без використання cpp,
а також без розкриття всіх директив # include і # define?

В: Вільно поширюються програми unifdef, rmifdef і scpp, які
роблять в точності те, що Вам потрібно. (Див. питання 17.12).

6.9 Як отримати список зумовлених ідентифікаторів?

В: Стандартного способу не існує, хоча необхідність виникає
часто. Якщо керівництво по компілятору не містить цих відомостей, то,
можливо, самий розумний шлях – виділити текстові рядки з
здійснимих файлів компілятора або препроцесора за допомогою утиліти
типу strings (1) системи Unix. Майте на увазі, що багато що залежать від
системи зумовлені ідентифікатори (наприклад, “unix”)
нестандартні (оскільки конфліктують з іменами користувача) і тому
такі ідентифікатори видаляються або змінюються.

6.10 Як написати cpp макрос зі змінним кількістю аргументів?

В: Популярна така уловка: визначити макрос з одним аргументом, і
викликати його з двома відкривають і двома закривають круглими
дужками:

               #define DEBUG(args) (printf(“DEBUG: “), printf args)

               if(n != 0) DEBUG((“n is %d
“, n));

Очевидний недолік такого підходу в тому, що потрібно пам’ятати про
додаткових круглих дужках. Інші рішення – використовувати
різні макроси (DEBUG1, DEBUG2, тощо) залежно від
кількості аргументів, або маніпулювати комами:

               #define DEBUG(args) (printf(“DEBUG: “), printf(args))
               #define _ ,
               DEBUG(“i = %d” _ i)

Часто краще використовувати цю функцію, яка
стандартним способом може використовувати змінне число аргументів.
Див питання 7.1 і 7.2.

7. Списки аргументів змінної довжини.

7.1 Як реалізувати функцію з перемінним числом аргументів?

В: Використовуйте головний файл (або, якщо необхідно, більше
старий ).

Ось приклад функції, яка об’єднує довільну кількість
стрінгів, розміщуючи результат у виділений за допомогою malloc ділянку
пам’яті.

# Include / * для malloc, NULL, size_t * /
# Include / * для va_ макросів * /
# Include / * для strcat і т.п. * /

  char *vstrcat(char *first, …)
  {
  size_t len = 0;
  char *retbuf;
  va_list argp;
  char *p;
  if(first == NULL)
  return NULL;
  len = strlen(first);
  va_start(argp, first);
  while((p = va_arg(argp, char *)) != NULL)
  len += strlen(p);
  va_end(argp);
retbuf = malloc (len + 1); / * +1 для
  if(retbuf == NULL)
return NULL; / * помилка * /
  (void)strcpy(retbuf, first);
  va_start(argp, first);
  while((p = va_arg(argp, char *)) != NULL)
  (void)strcat(retbuf, p);
  va_end(argp);
  return retbuf;
  }

Викликається функція приблизно так:

       char *str = vstrcat(“Hello, “, “world!”, (char *)NULL);

Зверніть увагу на явне приведення типу в останньому аргументі.
(Пам’ятаєте також, що після виклику такої функції потрібно звільнити
пам’ять).

Якщо компілятор розроблявся до прийому стандарту ANSI, перепишіть
визначення функції без прототипу (“char * vstrcat (first) char * first; {“)
дозволите замість , додайте

                    “extern char *malloc();” ,

і використовуйте int замість size_t. Можливо, доведеться видалити приведення
(Void) і використовувати varargs.h замість stdarg. Додаткові
міркування дивіться в наступному питанні.

Пам’ятайте, що в прототипах функцій зі списком аргументів змінної
довжини не вказується тип аргументів. Це означає, що за замовчуванням
буде відбуватися “розширення” типів аргументів (див. питання 5.8).
Це також означає, що тип нульового покажчика має бути явно вказано
(Див. питання 1.2).

Дивись: K & R II Розд. 7.3 c. 155, Розд. B7 c. 254; H & S
Розд. 13.4 c. 286-9; ANSI Розд 4.8 по 4.8.1.3.

7.2 Як написати функцію, яка б, подібно printf, отримувала рядок
формату і змінне число аргументів, а потім для виконання
більшої частини роботи передавала б все це printf?

В: Використовуйте vprintf, vfprintf, або vsprintf.

Перед Вами підпрограма “error”, яка після рядка “error:”
друкує повідомлення про помилку і символ нового рядка.

  #include <stdio.h>
  #include <stdarg.h>

  void
  error(char *fmt, …)
  {
  va_list argp;
  fprintf(stderr, “error: “);
  va_start(argp, fmt);
  vfprintf(stderr, fmt, argp);
  va_end(argp);
  fprintf(stderr, ”
“);
  }

Щоб використовувати старий головний файл замість ,
змініть заголовок функції

  void error(va_alist)
  va_dcl
  {
  char *fmt;

змініть рядок з va_start

  va_start(argp);

і додайте рядок

  fmt = va_arg(argp, char *);

між викликами va_start і vfprintf. Зауважте, що після va_dcl немає
крапки з комою.

Дивись: K & R II Розд. 8.3 c. 174, Розд. B1.2 c. 245; H & S
Розд. 17.12 c. 337; ANSI Розд. 4.9.6.7, 4.9.6.8, 4.9.6.9.

7.3 Як визначити, скільки аргументів передано функції?

В: Для переносимих програм така інформація недоступна. Деякі
старі системи мали нестандартну функцію nargs (), але її корисність
завжди була сумнівна, оскільки зазвичай ця функція повертає
кількість переданих машинних слів, а не число аргументів.
(Структури і числа з плаваючою точкою зазвичай передаються в декількох
словах).

Будь-яка функція з перемінним числом аргументів повинна бути здатна по
самим аргументам визначити їх число. Функції типу printf визначають
число аргументів по специфікатора формату (% d і т.п.) в рядку
формату (ось чому ці функції так кепсько поводяться при
розбіжності списку аргументів і рядки формату). Інший загальноприйнятий
прийом – використовувати ознака кінця списку (часто це числа 0, -1, або
нульовий покажчик, наведений до потрібного типу).
Дивись приклади функцій execl і vstrcat (питання 1.2 і 7.1).

7.4 Мені не вдається домогтися того, щоб макрос va_arg повертав аргумент
типу покажчик-на-функцію.

В: Маніпуляції з переписуванням типів, якими зазвичай займається
va_arg, закінчуються невдачею, якщо тип аргументу занадто складний –
зразок покажчика на функцію. Якщо, проте, використовувати typedef для
определния покажчика на функцію, то все буде нормально.

Дивись: ANSI Розд. 4.8.1.2 c. 124.

7.5 Як написати функцію з перемінним числом аргументів, яка передає
їх іншій функції зі змінним числом аргументів?

В: У випадку завдання нерозв’язна. В якості другої функції потрібно
використовувати таку, яка приймає покажчик типу va_list, як це
робить vfprintf в наведеному вище прикладі. Якщо аргументи повинні
передаватися безпосередньо (a не через покажчик типу va_list), і
друга функція приймає змінне число аргументів (і немає
можливості створити альтернативну функцію, приймаючу покажчик
va_list), то створення переносимої програми неможливо. Проблема
може бути вирішена, якщо звернутися до мови асемблера відповідної
машини.

7.6 Як викликати функцію зі списком аргументів, створюваним в процесі
виконання?

В: Не існує способу, який би гарантував переносимість. Якщо
у Вас допитливий розум, роздобудьте редактор таких списків, в ньому
є кілька безрозсудних ідей, які можна спробувати …
(Див. також питання 16.11).

8. Булеві вирази і змінні.

8.1 Змінні якого типу правильніше іспользоваль як булеві?
Чому в мові С немає стандартного типу логічних змінних?
Що використати для значень true і false – # define або enum?

В: У мові С немає стандартного типу логічних змінних, тому що
вибір конкретного типу грунтується або на економії пам’яті, або на
виграші часу. Такі питання краще вирішувати програмісту
(Використання типу int для булевої змінної може бути швидше,
тоді як використання типу char економить пам’ять).

Вибір між # define і enum – особиста справа кожного, і суперечки про те, що
краще, не особливо цікаві (Але все ж див. питання 9.1).
Використовуйте будь-який з чотирьох варіантів

  #define TRUE 1   #define YES 1
  #define FALSE 0   #define NO  0

  enum bool {false, true};   enum bool {no, yes};

або послідовно в межах програми чи проекту використовуйте
числа 1 і 0.
(Можливо, завдання булевих змінних через enum краще,
якщо використовуваний Вами відладчик розкриває вміст
enum-змінних).

Деякі вважають за краще такі способи завдання:

  #define TRUE (1==1)
  #define FALSE (!TRUE)

або задають “допоміжний” макрос

  #define Istrue(e) ((e) != 0)

Не видно, що вони цим виграють (див. питання 8.2, а також
питання 1.6).

8.2 Хіба не небезпечно ставити значення TRUE як 1, адже в С будь-яке не
рівне нулю значення розглядається як істинне? А якщо
оператор порівняння або вбудований логічний оператор поверне щось,
відмінне від 1?

В: Істинно (так-так!), що будь-яке ненульове значення розглядається в
З як значення “ІСТИНА”, але це може бути застосовано тільки “на вході”, де
очікується булева змінна. Коли булева змінна генерується
вбудованим оператором, гарантується, що вона дорівнює 0 або 1.
Отже, порівняння

    if((a == b) == TRUE)

як не смішно воно виглядає, буде вести себе, як очікується, якщо
значенням TRUE відповідає 1. Як правило, явні перевірки на TRUE
і FALSE небажані, оскільки деякі бібліотечні функції
(Варто згадати isupper, isalpha і т.п.), повертають в разі успіху
ненульове значення, яке _не обязательно_ дорівнює 1. (Крім того,
якщо Ви вірите, що “if ((a == b) == TRUE)” краще ніж “if (a == b)”,
то чому не піти далі і не написати

“if(((a == b) == TRUE) == TRUE)”?

Хороше “пальцеве” правило полягає в тому, щоб використовувати TRUE і
FALSE (або щось подібне) тільки коли булевим змінним або
аргументам функції присвоюються значення або коли значення
повертається булевой функцією, але ніколи при порівнянні.

Макровизначення препроцесора TRUE і FALSE використовуються для більшої
наочності, а не тому, що конкретні значення можуть змінитися.
(Див. також питання 1.7, 1.9).

Дивись: K & R I Розд. 2.7 c. 41; K & R II Розд. 2.6 c. 42,
Розд. A7.4.7 c. 204, Розд. A7.9 c. 206; ANSI Розд.
          3.3.3.3, 3.3.8, 3.3.9, 3.3.13, 3.3.14, 3.3.15, 3.6.4.1, 3.6.5…
… Чи наздожене Ахіллес і черепаху?


9. Структури, перерахування та об’єднання.

9.1 Яка різниця між enum-густо директив препроцесора # define?

В: В даний час різниця невелика. Хоча багато хто, можливо, вважали за краще
б інше рішення, стандарт ANSI стверджує, що безпідставного числу
елементів перерахування можуть бути явно привласнені цілочисельні
значення. (Заборона на присвоєння значень без явного приведення
типів, дозволив би при розумному використанні перерахувань уникнути
деяких помилок.)

Деякі переваги перерахувань в тому, що конкретні значення
задаються автоматично, що відладчик може представляти значення
перечислимого змінних в символьному вигляді, а також у тому, що
перечислимого змінні підпорядковуються звичайним правилам областей
дії. (Компілятор може також видавати предупреденія, коли
перерахування необдумано змішуються з цілочисельними змінними.
Таке змішання може розглядатися як прояв поганого стилю,
хоча формально це не заборонено). Недолік перерахувань в тому, що
у програміста мало можливостей керувати розміром змінних (і
попередженнями компілятора теж).

Дивись: K & R II Розд. 2.3 c. 39, Розд. A4.2 c. 196; H & S
Розд. 5.5 c. 100; ANSI Розд. 3.1.2.5, 3.5.2, 3.5.2.2.

9.2 Я чув, що структури можна копіювати як ціле, що вони можуть
бути передані функціям і повернуті ними, але в K & R I сказано, що
цього робити не можна.

В: В K & R I сказано лише, що обмеження на операції зі структурами
будуть зняті в наступних версіях компілятора; ці операції вже були
можливі в компіляторі Денніса Рітчі, коли видавалася книга K & R I.
Хоча деякі старі компілятори не підтримують копіювання
структур, всі сучасні компілятори мають таку можливість,
передбачену стандартом ANSI C, так що не повинно бути коливань
при копіюванні і передачі структур функцій.

Дивись: K & R I Розд. 6.2 c. 121; K & R II Розд. 6.2 c. 129; H & S
Розд. 5.6.2 c. 103; ANSI Розд. 3.1.2.5, 3.2.2.1, 3.3.16.

Читати 4 частина

Схожі статті:


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

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*