Мова C у запитаннях і відповідях. Частина 4 (FAQ)

9.3 Який механізм передачі і повернення структур?


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


Дивись: ANSI Разд.2.2.3 c. 13.

9.4 Ця програма працює правильно, але після завершення видає дамп
оперативної пам'яті. Чому?

  struct list
  {
  char *item;
  struct list *next;
  }

/ * Тут функція main * /

  main(argc, argv)
  …

В: З-за пропущеної точки з комою компілятор вважає, що main
повертає структуру. (Зв'язок структури з функцією main важко
визначити, заважає коментар). Так як для повернення структур
компілятор звичайно використовує в якості прихованого параметра
покажчик, код, згенерований для main () намагається прийняти три
аргументу, хоча передаються (в даному випадку стартовим кодом С)
тільки два. Див також питання 17.21.

9.5 Чому не можна порівнювати структури?

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

Якщо необхідно порівняти дві структури, напишіть для цього свою
власну функцію. C + + дозволить створити оператор ==, щоб
пов'язати його з Вашої функцією.

Дивись: K & R II Разд.6.2 c. 129; H & S Розд. 5.6.2 c. 103; ANSI
Rationale розд. 3.3.9 c. 47.

9.7 Як читати / писати структури з файлу / у файл?

В: Писати структури у файл можна безпосередньо за допомогою fwrite:

    fwrite((char *)&somestruct, sizeof(somestruct), 1, fp);

a Відповідний виклик fread прочитає структуру з файлу.
Однак файли, записані у такий спосіб будуть _не_ особливо стерпні
(Див. питання 9.11 і 17.3). Зауважте також, що на багатьох системах
потрібно використовувати у функції fopen прапор "b".

9.7 Мені попалася програма, у якій структура визначається так:

  struct name
  {
  int namelen;
  char name[1];
  };

потім йдуть хитрі маніпуляції з пам'яттю, щоб масив name вів себе
ніби в ньому кілька елементів. Такі маніпуляції законні / мобільні?

В: Такий прийом популярний, хоча Денніс Рітчі назвав це "дуже
фамільярним поводженням з реалізацією С ". ANSI вважає, що вихід за
межі оголошеного розміру члена структури не повністю відповідає
стандарту, хоча детальне обговорення всіх пов'язаних з цим проблем
не входить у завдання даних питань і відповідей. Схоже, однак, що
описаний прийом буде однаково добре прийнятий усіма відомими
реалізаціями С. (Компілятори, ретельно перевіряють межі масивів,
можуть видати попередження). Для страховки буде краще оголосити
змінну дуже великого розміру чим дуже малого. У нашому випадку

  …
  char name[MAXSIZE];
  …

де MAXSIZE більше, ніж довжина будь-якого імені, яке буде збережено
в масиві name []. (Є думка, що така модифікація буде
відповідати Стандарту).

Дивись: ANSI Rationale Розд. 3.5.4.2 c. 54-5.

9.8 Як визначити зміщення члена структури в байтах?

В: Якщо це можливо, необхідно використовувати макрос offsetof, який
визначений стандартом ANSI; див. <stddef.h>. Якщо макрос відсутня,
пропонується така (не на всі 100% мобільний) його реалізація

#define offsetof(type, mem) ((size_t)
((char *)&((type *) 0)->mem – (char *)((type *) 0)))

Для деяких компіляторів використання цього макросу може виявитися
незаконним.
Про те, як використовувати offsetof (), дивися наступне питання.

Дивись: ANSI Розд. 4.1.5, Rationale Розд. 3.5.4.2 c. 55.

9.9 Як здійснити доступ до членів структур за їхніми іменами під час
виконання програми?

В: Створіть таблицю імен та зсувів, використовуючи макрос offsetof ().
Зсув члена структури b в структурі типу a одно

  offsetb = offsetof(struct a, b)

Якщо structp вказує на початок структури, а b – член структури типу
int, зсув якого отримано вище, b може бути встановлений
побічно за допомогою

  *(int *)((char *)structp + offsetb) = value;

9.10 Чому sizeof видає більший розмір структурного типу, ніж я очікував,
як ніби в кінці структри зайві символи?

В: Це відбувається (можливі також внутрішні "діри"; див.
також питання 9.5), коли необхідно вирівнювання при завданні масиву
безперервних структур.

9.11 Мій компілятор залишає дірки в структурах, що призводить до втрати
пам'яті і перешкоджає "бінарного" введення / висновку при роботі з
зовнішніми файлами. Можу я відключити "дирообразованіе" або якось
контролювати вирівнювання?

В: У Вашому компіляторі, можливо, є розширення, (наприклад,
# Pragma), яке дозволить це зробити, але стандартного способу не
існує. Див також питання 17.3.

9.12 Чи можна задавати початкові значення об'єднань?

В: Стандарт ANSI допускає ініціалізацію першого члена об'єднання.
Не існує стандартного способу ініціалізації інших членів.
(І тим більше немає такого способу для старих компіляторів, які
взагалі не підтримують будь-якої ініціалізації).

9.13 Як передати функциии структуру, у якій всі члени – константи?

В: Оскільки в мові С немає можливості створювати безіменні значення
структурного типу, необхідно створити тимчасову структуру.

10. Декларації

10.1 Який тип цілочисельний змінної використовувати?

В: Якщо можуть знадобитися великі числа, (більше 32767 або менше
-32767), Використовуйте тип long. Якщо ні, і важлива економія пам'яті
(Великі масиви або багато структур), використовуйте short. У всіх
інших випадках використовуйте int. Якщо важливо точно визначити
момент переповнення та / або знак числа не має значення, використовуйте
відповідний тип unsigned. (Але будьте уважні при спільному
використанні типів signed і unsigned у виразах). Схожі
міркування застосовні при виборі між float і double.

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

Ці правила, очевидно, не застосовні до адрес змінних, оскільки
адреса повинна мати цілком певний тип.

Якщо необхідно оголосити змінну _определенного_ розміру,
(Єдиною причиною тут може бути спроба задовольнити зовнішнім
вимогам щодо організації пам'яті; див., крім того, питання 17.3),
неодмінно ізолюйте оголошення відповідним typedef.

10.2 Яким повинен бути новий 64-бітний тип на нових 64-бітових машинах?

Про: Деякі постачальники З компіляторів для 64-бітових машин підтримують
тип long int довжиною 64 біта. Інші ж, побоюючись, що занадто багато
вже написані програми залежать від sizeof (int) == sizeof (long) == 32
біта, вводять новий 64-бітний тип long long (або __longlong).

Програмісти, які бажають писати мобільні програми, повинні,
отже, ізолювати 64-бітові типи за допомогою засобу typedef.
Розробники компіляторів, які відчувають необхідність ввести новий
цілочисельний тип більшого розміру, повинні оголосити його як "має
принаймні 64 біт "(це дійсно новий тип, якого немає
в традиційному С), а не як "має точно 64 біт".

10.3 У мене зовсім не виходить визначення пов'язаного списку. Я пишу

  typedef struct
  {
  char *item;
  NODEPTR next;
  } *NODEPTR;

але компілятор видає повідомлення про помилку. Може структура в С
містити посилання на себе?

В: Структури в С, звичайно ж, можуть містити вказівники на себе;
обговорення цього питання і приклад у параграфі 6.5 K & R цілком
прояснюють це питання. У наведеному тексті проблема полягає в тому,
що визначення NODEPTR не закінчено в тому місці, де об'явлется
член структури "next". Для виправлення, забезпечите спочатку структуру
тегом ("struct node"). Далі оголосіть "next" як "struct node
* Next; ", і / або помістіть декларацію typedef цілком до або цілком
після оголошення структури. Одне з можливих рішень буде таким:

  struct node
  {
  char *item;
  struct node *next;
  };

  typedef struct node *NODEPTR;

Є принаймні три інших однаково правильних способу
зробити те ж саме.
Подібна проблема, яка вирішується приблизно так само, може виникнути
при спробі визначити за допомогою засобу typedef пару cсилающіхся
один на одного структур.

Дивись: K & R I Розд. 6.5 c. 101; K & R II Розд. 6.5 c. 139; H & S
Розд. 5.6.1 c. 102; ANSI Розд. 3.5.2.3.

10.4 Як оголосити масив з N покажчиків на функції, які повертають
вказівники на функції повертають покажчики на char?

В: Є принаймні три варіанти відповіді:

  1.  char *(*(*a[N])())();

2. Писати декларації по кроках, використовуючи typedef:

typedef char * pc; / * вказівник на char * /
typedef pc fpc (); / * функція, що повертає покажчик на char * /
typedef fpc * pfpc; / * вказівник на .. див. вище * /
typedef pfpc fpfpc (); / * функція, що повертає … * /
typedef fpfpc * pfpfpc; / * вказівник на … * /
pfpfpc a [N]; / * масив … * /

3. Використовувати програму cdecl, яка перекладає з англійської
на С і навпаки.

  cdecl> declare a as array of pointer to function returning
   pointer to function returning pointer to char
  char *(*(*a[])())()

cdecl може також пояснити складні декларації, допомогти при явному
приведення типів, і, для випадку складних декларацій, наче тільки-що
розібраного, показати набір круглих дужок, в які укладені
аргументи. Версії cdecl можна знайти в comp.sources.unix (див. питання
17.12) і в K & R II. Будь-яка гарна книга по С повинна пояснювати, як
для розуміння складних декларацій, читати їх "зсередини назовні",
("Декларація нагадує використання").

Дивись: K & R II Розд. 5.12 c. 122; H & S Розд. 5.10.1 c. 116.

10.5 Я моделюю Марківський процес з кінцевим числом станів, і в мене
є набір функцій для кожного стану. Я хочу, щоб зміна
станів відбувалася шляхом повернення функцією покажчика на функцію,
соответветствующую наступного стану. Однак, я виявив
обмеження у механізмі декларацій мови З: немає можливості оголосити
функцію, що повертає покажчик на функцію, що повертає покажчик
на функцію, що повертає покажчик на функцію …

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

10.6 Мій компілятор видає повідомлення про невірну повторної декларації,
хоча я тільки раз визначив функцію і лише раз викликав.

В: Мається на увазі, що функції, що викликаються без декларації в області
видимості (або до такої декларації), повертають значення типу int.
Це призведе до протиріччя, якщо згодом функція декларована
інакше. Якщо функція повертає неціле значення, вона повинна бути
оголошена до того як буде викликано.

Дивись: K & R I Розд. 4.2 c. 70; K & R II Розд. 4.2 c. 72; ANSI
Розд. 3.3.2.2.

10.7 Як найкращим чином декларувати і визначити глобальні
змінні?

В: Перш за все відмітимо, що хоча може бути багато _декларацій_ (і багато в
багатьох файлах) однієї "глобальної" (строго кажучи "зовнішньої")
змінної, (або функції), має бути лише одне _определеніе_.
(Визначення – це така декларація, при якій дійсно
виділяється пам'ять для змінної, і присвоюється, якщо потрібно,
початкове значення). Краще всього помістити визначення в якійсь
головний (для програми або її частини). c файл, з зовнішньої декларацією в
головному файлі. h, який при необхідності підключається за допомогою
# Include. Файл, в якому знаходиться визначення змінної, також
повинен включати головний файл з зовнішньої декларацією, щоб компілятор
міг перевірити відповідність декларації та визначення.

Це правило забезпечує високу мобільність програм і знаходиться в
згідно з вимогами стандарту ANSI C. Зауважте, що багато
компілятори і компонувальники в системі UNIX використовують "загальну модель",
яка дозволяє багаторазові визначення без ініціалізації.
Деякі дуже дивні компілятори можуть вимагати явної
ініціалізації, щоб відрізнити ухвалу від зовнішньої декларації.

За допомогою препроцесорну трюку можна влаштувати так, що декларація
буде зроблена лише одного разу, в головному файлі, і вона c допомогою # define
"Перетвориться" на визначення точно при одному включенні головного
файлу.

Дивись: K & R I Розд. 4.5 c. 76-7; K & R II Розд. 4.4 c. 80-1;
ANSI Розд. 3.1.2.2 (особливо Rationale), Розд. 3.7, 3.7.2,
Розд. F.5.11; H & S Розд. 4.8 c. 79-80; CT & P Розд. 4.2 c. 54-56.

10.8 Що означає ключове слово extern при декларації функції?

Про: слово extern при декларації функції може бути використано з
міркувань хорошого стилю для вказівки на те, що визначення
функції, можливо, знаходиться в іншому файлі. Формально між

  extern int f();
і
  int f();

немає ніякої різниці.

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

10.9 Я, нарешті, зрозумів, як об'влять покажчики на функції, але як їх
ініціалізувати?

В: Використовуйте щось таке

  extern int func();
  int (*fp)() = func;

Коли ім'я функції з'являється у виразі, але функція не викликається
(Тобто, за ім'ям функції не слід "("), воно "згортається",
як і у випадку масивів, в покажчик (тобто неявним чином записаний
адреса).

Явна оголошення функції звичайно необхідно, так як неявного
оголошення зовнішньої функції в даному випадку не відбувається (знову-таки
через те, що за ім'ям функції не слід "(").

10.10 Я бачив, що функції викликаються за допомогою покажчиків і просто як
функції. У чому справа?

В: За початковим задумом творця З покажчик на функцію має
був "перетворитися" у справжню функцію за допомогою оператора *
та додаткової пари круглих дужок для правильної інтерпретації.

  int r, func(), (*fp)() = func;
  r = (*fp)();

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

  r = fp();

працює однаково правильно, незалежно від того, що таке fp –
функція або покажчик на неї. (Ім'я завжди використовується однозначно;
просто неможливо зробити щось інше з покажчиком на функцію,
за яким слідує список аргументів, окрім як викликати функцію).
Явна завдання * безпечно і все ще дозволено (і рекомендується,
якщо важлива сумісність зі старими компіляторами).

Дивись: ANSI Розд. 3.3.2.2 c. 41, Rationale c. 41.

10.11 Де може стати в нагоді ключове слово auto?

В: Ніде, воно вийшло з ужитку.

11. Стандартний ввід / вивід.

11.1 Що поганого в таких рядках:

  char c;
  while((c = getchar()) != EOF)…

В: По-перше, змінна, якій присвоюється повернене getchar
значення, повинна мати тип int. getchar може повернути всі можливі
значення для символів, в тому числі EOF. Якщо значення, повернене
getchar присвоюється змінної типу char, можливе або звичайну
літеру прийняти за EOF, або EOF спотвориться (особливо якщо використовувати
тип unsigned char) так, що розпізнати його буде неможливо.

Дивись: CT & P Разд.5.1 c. 70.

11.2 Як надрукувати символ "%" у рядку формату printf? Я спробував
\%, Але з цього нічого не вийшло.

В: Просто подвійте знак відсотка%%.

Дивись: K & R I Розд. 7.3 c. 147; K & R II Розд. 7.2 c. 154; ANSI
Розд. 4.9.6.1.

11.3 Чому не працює scanf ("% d", i)?

В: Для функції scanf необхідні адреси змінних, за якими будуть
записані дані, потрібно написати scanf ("% d", & i);

11.4 Чому не працює

  double d;
  scanf(“%f”, &d);

В: scanf використовує специфікацію формату% lf для значень типу double
і% f для значень типу float. (Зверніть увагу на відмінність з
printf, де відповідно до правила розширення типів аргументів
специфікація% f використовується як для float, так і для double).

11.5 Чому фрагмент програми

  while(!feof(infp)) {
  fgets(buf, MAXLINE, infp);
  fputs(buf, outfp);
  }

двічі копіює останній рядок?

В: Це Вам не Паскаль. Символ EOF з'являється тільки _после_ спроби
прочитати, коли функція введення натикається на кінець файлу.
Частіше за все необхідно просто перевіряти значення, що повертається
функцією введення, (у нашому випадку fgets); у використанні feof ()
зазвичай взагалі немає необхідності.

11.6 Чому всі проти використання gets ()?

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

11.7 Чому змінної errno присвоюється значення ENOTTY після виклику
printf()?

В: Багато реалізації стандартної бібліотеки вводу / виводу кілька
змінюють свою поведінку, якщо стандартний пристрій виводу –
термінал. Щоб визначити тип пристрою, виконується операція,
яка закінчується невдало (c повідомленням ENOTTY), якщо пристрій
висновку – не термінал. Хоча висновок завершується успішно, errno все ж
містить ENOTTY.

Дивись: CT & P Розд. 5.4 c. 73.

11.8 Запити моєї програми, а також проміжні результати не завжди
відображаються на екрані, особливо коли моя програма передає дані
по каналу (pipe) іншій програмі.

В: Найкраще явно використовувати fflush (stdout), коли неодмінно
потрібно бачити те, що видає програма. Кілька механізмів намагаються
"В потрібний час" здійснити fflush, але, схоже, все це правильно
працює в тому випадку, коли stdout – це термінал. (Див. питання
        11.7).

11.9 При читанні з клавіатури функцією scanf виникає почуття, що
програма зависає, поки я перекладаю рядок.

В: Функція scanf була задумана для введення у вільному форматі,
необхідність у якому виникає рідко при читанні з клавіатури.
Що ж стосується відповіді на питання, то символ ""У ланцюжку формату рядку
зовсім не означає, що scanf буде чекати перекладу рядка. Це
значить, що scanf буде читати і відкидати всі зустрілися поспіль
пробільні літери (тобто символи пропуску, табуляції, нового рядка,
повернення каретки, вертикальної табуляції і нової сторінки).
Схоже утруднення трапляється, коли scanf "застрягає", отримавши
несподівано для себе нечислові дані. Через подібних проблем часто
краще читати всю рядок за допомогою fgets, а потім використовувати sscanf
або інші функції, що працюють з рядками, щоб інтерпретувати
введений рядок по частинах. Якщо використовується sscanf, не забудьте
перевірити значення, що повертається для впевненості в тому, що число
прочитаних змінних дорівнює очікуваному.

11.10 Я намагаюся оновити вміст файлу, для чого використовую fopen в
режимі "r +", далі читаю рядок, потім пишу модифіковану рядок
у файл, але у мене нічого не виходить.

В: Неодмінно викличте fseek перед записом у файл. Це робиться
для повернення до початку рядка, яку Ви хочете переписати; крім
того, завжди необхідно викликати fseek або fflush між читанням і
записом при читанні / запису в режимах "+". Пам'ятайте також, що літери
можна замінити лише точно таким же числом літер. Див також
питання 17.4.

Дивись: ANSI Розд. 4.9.5.3 c. 131.

11.11 Як мені прочитати одну літеру, не чекаючи натискання RETURN?

В: Дивись питання 16.1

11.12 Як мені скасувати очікуваний введення, так, щоб дані, введені
користувачем, не читалися при наступному запиті? Чи допоможе тут
fflush(stdin)?

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

11.13 Як перенаправити stdin або stdout у файл?

В: Використовуйте freopen.

11.14 Якщо я використовував freopen, то як повернутися назад до stdout (stdin)?

В: Якщо необхідно перемикатися між stdin (stdout) і файлом,
найкраще універсальне рішення – не поспішати використовувати freopen.
Спробуйте використовувати покажчик на файл, якому можна за бажанням
привласнити те чи інше значення, залишаючи значення stdout (stdin)
недоторканим.

11.15 Як відновити ім'я файлу за вказівником на відкритий файл?

В: Це проблема, взагалі кажучи, нерозв'язна. У разі операційної
системи UNIX, наприклад, буде потрібно пошук по всьому диску (який,
можливо, зажадає спеціального дозволу), і цей пошук закінчиться
невдало, якщо покажчик на файл був каналом (pipe) або був пов'язаний
з віддаленим файлом. Крім того, оманливий відповідь буде отримано для
файлу з множинними зв'язками. Краще за все самому запам'ятовувати імена
при відкритті файлів (можливо, використовуючи спеціальні функції,
викликаються до і після fopen);

12. Бібліотечні функції.

12.1 Чому strncpy не завжди завершує рядок-результат символом ""?

В: strncpy була задумана для обробки тепер вже застарілих структур
даних – "рядків" фіксованої довжини, не обов'язково завершуються
символом "". І, треба сказати, strncpy не зовсім зручно
використовувати в інших випадках, оскільки часто доводиться додавати
символ "" вручну.

12.2 Я намагаюся сортувати масив рядків за допомогою qsort, використовуючи
для порівняння strcmp, але у мене нічого не виходить.

В: Коли Ви говорите про "масиві рядків", то, мабуть, маєте на увазі
"Масив покажчиків на char". Аргументи функції порівняння, що працює
в парі з qsort – це покажчики на порівнювані об'єкти, в даному
випадку – покажчики на покажчики на char. (Звичайно, strcmp працює
просто з покажчиками на char).

Аргументи процедури порівняння описані як "узагальнені вказівники"
const void * або char *. Вони повинні бути перетворені на те, що вони
представляють насправді, тобто (Char **) і далі потрібно розкрити
посилання за допомогою *; тоді strcmp отримає саме те, що потрібно для
порівняння. Напишіть функцію порівняння приблизно так:

int pstrcmp (p1, p2) / * порівняти рядки, використовуючи покажчики * /
char * p1, p2 * / * const void * для ANSI C * /
  {
              return strcmp(*(char **)p1, *(char **)p2);
  }

Майте на увазі, що в K & R II Розд. 5.11 обговорюється функція qsort,
яка відрізняється від стандартної.

12.3 Зараз я намагаюся сортувати масив структур за допомогою qsort.
Процедура порівняння, яку я використовую, приймає як
аргументів покажчики на структури, але компілятор видає повідомлення
про невірний типі функції порівняння. Як мені перетворити аргументи
функції, щоб придушити повідомлення про помилку?

В: Перетворення повинні бути зроблені всередині функци порівняння, яка
повинна бути оголошена як приймаюча аргументи на кшталт "узагальнених
покажчиків (const void * або char *) як це описано в питанні 12.2.
Функція порівняння може виглядати так:

   int mystructcmp(p1, p2)
char * p1, p2 * / * const void * для ANSI C * /
   {
   struct mystruct *sp1 = (struct mystruct *)p1;
   struct mystruct *sp2 = (struct mystruct *)p2;

/ * Тепер порівнюйте sp1-> що-завгодно і sp2-> … * /
   }

(З іншого боку, якщо сортуються покажчики на структури,
необхідна непряма адресація, як у питанні 12.2:

                    sp1 = *(struct mystruct **)p1 .)

12.4 Як перетворити числа в рядки (операція, протилежна atoi)?
Чи є функція itoa?

В: Просто використовуйте sprintf. (Необхідно буде виділити пам'ять для
результату, див. питання 3.1 та 3.2. Турбуватися, що sprintf –
занадто сильний засіб, яке може призвести до перевитрати
пам'яті та збільшення часу виконання, немає підстав. На практиці
sprintf працює добре).

Дивись: K & R I Разд.3.6 c. 60; K & R II Разд.3.6 c. 64.

12.5 Як отримати дату або час в С програмі?

В: Просто використовуйте функції time, ctime, та / або localtime. (Ці
функції існують багато років, вони включені в стандарт ANSI).
Ось простий приклад:

  #include <stdio.h>
  #include <time.h>

  main()
  {
  time_t now = time((time_t *)NULL);
  printf(“It”s %.24s.
“, ctime(&now));
  return 0;
  }

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

12.6 Я знаю, що бібліотечна функція localtime розбиває значення time_t
по окремих членів структури tm, а функція ctime перетворює time_t в
рядок символів. А як пройти зворотну операцію перекладу
структури tm або рядки символів в значення time_t?

В: Стандарт ANSI визначає бібліотеную функцію mktime, яка
перетворює структуру tm в time_t. Якщо Ваш компілятор не
підтримує mktime, скористайтеся однією із загальнодоступних версій
цієї функції.

Переклад рядка в значення time_t виконати складніше через велику
кількості форматів дат і часу, які повинні бути розпізнані.
Деякі компілятори підтримують функцію strptime; інша
популярна функція – partime широко поширюється з пакетом RCS,
але немає впевненості, що ці функції увійдуть до Стандарт.

Дивись: K & R II Розд. B10 c. 256; H & S Розд. 20.4 c. 361; ANSI
Розд. 4.12.2.3.

12.7 Як додати n днів до дати? Як обчислити різницю двох дат?

В: Що увійшли до стандарт ANSI / ISO функції mktime і difftime можуть допомогти
при вирішенні обох проблем. mktime () підтримує ненормалізоване
дати, тобто можна прямо узяти заповнену структуру tm, збільшити або
зменшити член tm_mday, потім викликати mktime (), щоб нормалізувати
члени year, month, і day (і перетворити на значення time_t).
difftime () обчислює різницю в секундах між двома величинами
типу time_t. mktime () можна використовувати для обчислення значення
time_t різниці двох дат. (Зауважте, однак, що всі ці прийоми
можливі лише для дат, які можуть бути представлені значенням
типу time_t; крім того, через переходів на літній і зимовий час
тривалість дня не точно дорівнює 86400 сек.).
Cм. також питання 12.6 і 17.28.

Дивись: K & R II Розд. B10 c. 256; H & S Розд. 20.4, 20.5
c. 361-362; ANSI Розд. 4.12.2.2, 4.12.2.3.

12.8 Мені потрібен генератор випадкових чисел.

В: У стандартній бібліотеці С є функція rand (). Реалізація цієї
функції у Вашому компіляторі може не бути ідеальною, але й створення
кращої функції може виявитися дуже непростим.

Дивись: ANSI Розд. 4.10.2.1 c. 154; Knuth Vol. 2 Chap. 3
          c. 1-177.

12.9 Як отримати випадкові цілі числа в певному діапазоні?

В: Очевидний спосіб

  rand() % N

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

(Int) ((double) rand () / ((double) RAND_MAX + 1) * N)

Якщо Вам не подобається вживання чисел з плаваючою точкою,
спробуйте

  rand() / (RAND_MAX / N + 1)

Обидва методи вимагають знання RAND_MAX (згідно ANSI, RAND_MAX визначено
в <stdlib.h>. Передбачається, що N багато менше RAND_MAX.

12.10 Кожен раз при запуску програми функція rand () видає одну й ту ж
послідовність чисел.

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

Дивись: ANSI Розд. 4.10.2.2 c. 154.

12.11 Мені необхідна випадкова величина, що має два значення true / false.
Я використовую rand ()% 2, але виходить невипадкова послідовність
0,1,0,1,0….

В: Неякісні генератори випадкових чисел (потрапили, на жаль, в
складу деяких компіляторів) не дуже то випадкові, коли мова
йде про молодших бітах. Спробуйте використовувати старші біти.
Див питання 12.9.

12.12 Я намагаюся перенести на О: Ці підпрограми в різній
іншу систему стару мірою застаріли. Необхідно
програму. Чому я використовувати
отримую повідомлення
“undefined external”
для

index? використовуйте strchr.
rindex? використовуйте strrchr.
bcopy? використовуйте memmove,
помінявши місцями перший
і другий аргументи (див. також
питання 5.15).
bcmp? використовуйте memcmp.
bzero? використовуйте memset, з другим
аргументом, рівним 0.

12.13 Я весь час отримую повідомлення про помилки – не визначено бібліотечні
функції, але я включаю всі необхідні головні файли.

В: Іноді (особливо для нестандартних функцій) випливає явно вказувати,
які бібліотеки потрібні при компонуванні програми.
Див також питання 15.2.

12.14 Я і раніше, отримую повідомлення, що бібліотечні функції не
визначено, хоча і використовую ключ-l, щоб явно вказати бібліотеки
під час перегляду.

В: Багато компонувальники роблять один прохід за списком об'єктних файлів
і бібліотек, які Ви вказали, витягуючи з бібліотек тільки ті
функції, що задовольняють посилання, які _к цього моменту_ виявилися
невизначеними. Отже, порядок щодо об'єктних файлів, в якому перераховані бібліотеки, важливий; зазвичай перегляд бібліотек
потрібно робити в самому кінці. (Наприклад, в операційній системі UNIX
розміщуйте ключі-l в самому кінці командного рядка).

Читати 5 частина

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


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

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

Ваш отзыв

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

*

*