C
C — це компільована мова програмування загального призначення, яка широко використовується для системного програмування, embedded-розробки, операційних систем, драйверів, компіляторів, бібліотек, мережевого програмування, високопродуктивних застосунків і низькорівневої роботи з пам’яттю.
Мова C поєднує відносно простий синтаксис із дуже високим рівнем контролю над пам’яттю, процесором, структурами даних і системними ресурсами.
Основна ідея: C дає програмісту прямий контроль над пам’яттю й виконанням програми, тому її часто використовують там, де важливі швидкість, передбачуваність і близькість до апаратного рівня.
Загальний опис
Мова програмування C є однією з найвпливовіших мов в історії програмування. Вона стала основою для багатьох інших мов і технологій.
На C або з сильним впливом C створювалися:
- операційні системи;
- ядра систем;
- компілятори;
- драйвери;
- embedded firmware;
- мережеві сервіси;
- бази даних;
- інтерпретатори мов;
- системні бібліотеки;
- графічні бібліотеки;
- високопродуктивні обчислювальні модулі.
Перевага: C залишається важливою мовою, тому що дозволяє писати компактний, швидкий і близький до апаратного забезпечення код.
Історія мови C
Мова C була створена на початку 1970-х років у Bell Labs. Її розвиток пов’язаний із розробкою операційної системи Unix.
C стала практичною мовою для системного програмування, тому що давала:
- достатньо високий рівень абстракції;
- доступ до пам’яті;
- можливість працювати з адресами;
- ефективний машинний код після компіляції;
- переносимість між різними платформами;
- компактний синтаксис.
Важливо: C створювалася як практична мова для реальних системних задач, тому в ній багато можливостей, які дають силу, але потребують дисципліни.
Стандарти C
Мова C має кілька стандартів.
Відомі версії:
- K&R C — рання форма мови, описана в книзі Kernighan і Ritchie;
- ANSI C або C89/C90 — перший офіційний стандартизований варіант;
- C99 — додав нові можливості, зокрема `//` коментарі, `long long`, `inline`;
- C11 — додав покращення для багатопоточності, atomics та інші зміни;
- C17 — коригувальний стандарт;
- C23 — сучасніший стандарт із новими покращеннями.
Практична порада: у проєкті потрібно явно знати, під який стандарт C пишеться код: C89, C99, C11, C17 або C23.
Компіляція
C — це компільована мова. Це означає, що вихідний код спочатку перетворюється компілятором на машинний код або об’єктні файли.
Типовий процес:
- Preprocessing.
- Compilation.
- Assembly.
- Linking.
- Отримання виконуваного файлу.
Приклад:
gcc main.c -o main
./main
Суть компіляції: код C перетворюється на виконувану програму до запуску, тому C може бути дуже швидкою.
GCC
GCC або GNU Compiler Collection — один із найвідоміших компіляторів C.
GCC використовується для:
- компіляції C-коду;
- системного програмування;
- Linux-розробки;
- embedded toolchains;
- оптимізації коду;
- створення об’єктних файлів;
- linking;
- cross-compilation.
Приклад:
gcc -Wall -Wextra -std=c11 main.c -o app
Практична роль: GCC часто використовується в Linux, embedded і open-source проєктах.
Clang
Clang — сучасний компілятор C, C++ і Objective-C, побудований на LLVM.
Clang відомий:
- хорошими повідомленнями про помилки;
- швидкою компіляцією;
- підтримкою сучасних стандартів;
- інтеграцією з tooling;
- static analysis;
- форматуванням і перевірками коду;
- використанням у різних IDE.
Приклад:
clang -Wall -Wextra -std=c17 main.c -o app
Перевага: Clang часто зручний для розробки, тому що дає зрозумілі diagnostics і добре інтегрується з інструментами аналізу.
Перша програма на C
Класичний приклад програми на C:
#include <stdio.h>
int main(void) {
printf("Hello, world!\n");
return 0;
}
У цьому прикладі:
- `#include <stdio.h>` підключає стандартну бібліотеку введення-виведення;
- `main` — головна функція програми;
- `printf` виводить текст;
- `return 0` означає успішне завершення програми.
Суть прикладу: кожна C-програма має точку входу, і зазвичай це функція `main`.
Синтаксис
C має компактний синтаксис, який вплинув на багато інших мов: C++, Java, JavaScript, C#, Go, Rust і багато інших.
Основні елементи синтаксису:
- змінні;
- типи даних;
- оператори;
- умови;
- цикли;
- функції;
- масиви;
- вказівники;
- структури;
- макроси;
- header files.
Важливо: синтаксис C здається простим, але багато складності приховано в роботі з пам’яттю, вказівниками й undefined behavior.
Типи даних
У C є базові типи даних.
Основні типи:
- `char`;
- `short`;
- `int`;
- `long`;
- `long long`;
- `float`;
- `double`;
- `long double`;
- `_Bool`;
- `void`.
Приклад:
int age = 25;
double price = 19.99;
char grade = 'A';
Практична роль: у C важливо розуміти розмір типів, діапазон значень і поведінку при переповненні.
Змінні
Змінна — це іменована область пам’яті для зберігання значення.
Приклад:
int count = 10;
count = count + 1;
Змінні в C мають:
- тип;
- ім’я;
- значення;
- область видимості;
- час життя;
- адресу в пам’яті.
Суть змінної: у C змінна — це не просто ім’я, а конкретне місце в пам’яті з певним типом.
Константи
Константи використовуються для значень, які не повинні змінюватися.
Приклад:
const double PI = 3.1415926535;
Також у C часто використовуються макроси:
#define MAX_USERS 100
Порада: для типізованих констант краще використовувати `const`, а макроси залишати для випадків, де вони справді потрібні.
Оператори
C має багато операторів.
Основні групи:
- арифметичні: `+`, `-`, `*`, `/`, `%`;
- порівняння: `==`, `!=`, `<`, `>`, `<=`, `>=`;
- логічні: `&&`, `||`, `!`;
- побітові: `&`, `|`, `^`, `~`, `<<`, `>>`;
- присвоєння: `=`, `+=`, `-=`, `*=`;
- інкремент і декремент: `++`, `--`;
- адреса й розіменування: `&`, `*`.
Практична роль: C дає низькорівневі оператори, зокрема побітові операції, які важливі для системного й embedded програмування.
Умови
Умовні оператори дозволяють виконувати різний код залежно від умови.
Приклад:
if (age >= 18) {
printf("Adult\n");
} else {
printf("Minor\n");
}
Також є `switch`:
switch (status) {
case 1:
printf("Active\n");
break;
case 2:
printf("Blocked\n");
break;
default:
printf("Unknown\n");
}
Суть умов: програма може змінювати поведінку залежно від значень змінних і результатів перевірок.
Цикли
У C є кілька типів циклів.
`for`:
for (int i = 0; i < 5; i++) {
printf("%d\n", i);
}
`while`:
while (count > 0) {
count--;
}
`do while`:
do {
printf("Run once\n");
} while (condition);
Практична роль: цикли потрібні для повторення дій: обробки масивів, читання даних, обчислень і роботи з потоками.
Функції
Функція — це іменований блок коду, який може отримувати аргументи й повертати результат.
Приклад:
int add(int a, int b) {
return a + b;
}
int main(void) {
int result = add(2, 3);
printf("%d\n", result);
return 0;
}
Функції допомагають:
- структурувати код;
- повторно використовувати логіку;
- тестувати частини програми;
- зменшувати дублювання;
- розділяти відповідальність.
Суть функції: функція перетворює вхідні дані на результат або виконує певну дію.
Масиви
Масив — це послідовність елементів одного типу.
Приклад:
int numbers[5] = {1, 2, 3, 4, 5};
printf("%d\n", numbers[0]);
Особливості масивів у C:
- індексація починається з 0;
- розмір часто потрібно контролювати вручну;
- вихід за межі масиву небезпечний;
- масиви тісно пов’язані з вказівниками.
Критично: C не перевіряє межі масиву автоматично. Вихід за межі може спричинити помилки, вразливості або аварійне завершення програми.
Рядки
У C рядок — це масив символів, який завершується нульовим символом `'\0'`.
Приклад:
char name[] = "Alice";
printf("%s\n", name);
Рядок фактично виглядає так:
A l i c e \0
Важливо: робота з рядками в C потребує уважності, тому що потрібно враховувати розмір буфера і завершальний символ `\0`.
Вказівники
Вказівник або pointer — це змінна, яка зберігає адресу іншої змінної в пам’яті.
Приклад:
int value = 10;
int *ptr = &value;
printf("%d\n", *ptr);
У цьому прикладі:
- `&value` отримує адресу змінної;
- `ptr` зберігає адресу;
- `*ptr` отримує значення за адресою.
Суть вказівника: pointer дозволяє працювати не лише зі значенням, а й з адресою, де це значення зберігається.
Null pointer
NULL pointer — це вказівник, який не вказує на дійсний об’єкт.
Приклад:
int *ptr = NULL;
if (ptr != NULL) {
printf("%d\n", *ptr);
}
Критично: розіменування `NULL` pointer є помилкою й може призвести до аварійного завершення програми.
Pointer arithmetic
У C можна виконувати арифметику вказівників.
Приклад:
int numbers[] = {10, 20, 30};
int *p = numbers;
printf("%d\n", *(p + 1));
`p + 1` означає перехід до наступного елемента типу `int`, а не просто збільшення адреси на 1 байт.
Увага: pointer arithmetic дуже потужна, але помилки в ній можуть пошкодити пам’ять або створити вразливості.
Структури
struct дозволяє об’єднати кілька полів у один тип.
Приклад:
struct User {
int id;
char name[50];
};
int main(void) {
struct User user = {1, "Alice"};
printf("%d %s\n", user.id, user.name);
return 0;
}
Структури використовуються для:
- опису об’єктів;
- системних структур;
- записів;
- конфігурацій;
- даних протоколів;
- API-структур;
- binary formats.
Практична роль: struct дозволяє створювати власні типи даних і групувати пов’язані поля.
typedef
typedef створює нове ім’я для існуючого типу.
Приклад:
typedef struct {
int id;
char name[50];
} User;
User user = {1, "Alice"};
Практична користь: typedef може зробити код коротшим і зручнішим для читання, особливо зі структурами.
enum
enum дозволяє описувати набір іменованих констант.
Приклад:
enum Status {
STATUS_NEW,
STATUS_ACTIVE,
STATUS_BLOCKED
};
enum Status status = STATUS_ACTIVE;
Суть enum: enum робить код зрозумілішим, коли потрібно працювати з обмеженим набором станів.
union
union дозволяє зберігати різні типи даних в одній області пам’яті.
Приклад:
union Value {
int i;
float f;
char c;
};
Union використовується в низькорівневих сценаріях:
- memory optimization;
- binary protocols;
- embedded;
- parsers;
- variant-like structures;
- hardware registers.
Важливо: union потребує дисципліни, тому що програміст має знати, яке саме поле зараз є активним.
Динамічна пам’ять
C дозволяє вручну виділяти й звільняти пам’ять.
Основні функції:
- `malloc`;
- `calloc`;
- `realloc`;
- `free`.
Приклад:
#include <stdlib.h>
int *numbers = malloc(5 * sizeof(int));
if (numbers == NULL) {
return 1;
}
numbers[0] = 10;
free(numbers);
numbers = NULL;
Критично: кожен успішний `malloc`, `calloc` або `realloc` має бути логічно завершений `free`, інакше виникає memory leak.
malloc і free
malloc виділяє блок пам’яті.
free звільняє пам’ять.
Приклад:
char *buffer = malloc(256);
if (buffer == NULL) {
return 1;
}
/* use buffer */
free(buffer);
buffer = NULL;
Після `free` не можна використовувати старий pointer як дійсний.
Небезпека: use-after-free, double free і memory leak є типовими помилками C-програм.
Header files
Header files мають розширення `.h` і використовуються для оголошень.
Наприклад:
/* math_utils.h */
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int add(int a, int b);
#endif
Файл реалізації:
/* math_utils.c */
#include "math_utils.h"
int add(int a, int b) {
return a + b;
}
Практична роль: header files відокремлюють інтерфейс модуля від реалізації.
Preprocessor
Preprocessor обробляє директиви перед компіляцією.
Приклади директив:
- `#include`;
- `#define`;
- `#ifdef`;
- `#ifndef`;
- `#endif`;
- `#pragma`.
Приклад include guard:
#ifndef CONFIG_H
#define CONFIG_H
#define MAX_SIZE 1024
#endif
Увага: макроси препроцесора не мають типів і можуть створювати складні для пошуку помилки.
Стандартна бібліотека C
Стандартна бібліотека C надає базові функції для:
- введення-виведення;
- роботи з рядками;
- роботи з пам’яттю;
- математичних обчислень;
- роботи з файлами;
- обробки символів;
- часу;
- сортування;
- перетворення типів.
Поширені header files:
- `<stdio.h>`;
- `<stdlib.h>`;
- `<string.h>`;
- `<math.h>`;
- `<ctype.h>`;
- `<time.h>`;
- `<stdint.h>`;
- `<stdbool.h>`;
- `<stddef.h>`.
Практична роль: стандартна бібліотека C невелика, але містить базові building blocks для багатьох програм.
Робота з файлами
C дозволяє працювати з файлами через `FILE*`.
Приклад:
#include <stdio.h>
int main(void) {
FILE *file = fopen("data.txt", "r");
if (file == NULL) {
perror("fopen");
return 1;
}
char line[256];
while (fgets(line, sizeof(line), file) != NULL) {
printf("%s", line);
}
fclose(file);
return 0;
}
Важливо: після відкриття файлу потрібно перевіряти помилки й закривати файл через `fclose`.
Робота з помилками
C часто використовує коди помилок.
Приклади:
- функція повертає `NULL`;
- функція повертає `-1`;
- `errno` містить код помилки;
- `perror` друкує опис помилки;
- власні enum-коди статусів.
Приклад:
FILE *file = fopen("missing.txt", "r");
if (file == NULL) {
perror("fopen");
return 1;
}
Практична роль: у C помилки часто потрібно перевіряти явно після кожного ризикованого виклику.
Undefined behavior
Undefined behavior — це ситуація, коли стандарт C не визначає, що має статися.
Приклади:
- вихід за межі масиву;
- розіменування NULL pointer;
- використання неініціалізованої змінної;
- signed integer overflow;
- use-after-free;
- порушення правил aliasing;
- неправильний формат у `printf`.
Критично: undefined behavior може працювати “нормально” під час тесту, а потім зламатися після оптимізації, зміни компілятора або запуску на іншій платформі.
Безпека в C
C дає багато контролю, але не захищає автоматично від багатьох помилок.
Типові ризики:
- buffer overflow;
- use-after-free;
- double free;
- memory leak;
- integer overflow;
- format string vulnerabilities;
- dangling pointers;
- uninitialized memory;
- race conditions;
- unsafe string functions.
Критично: C-код, який працює з мережею, файлами, користувацьким input або системними ресурсами, потребує особливо уважного security review.
Buffer overflow
Buffer overflow виникає, коли програма записує більше даних, ніж виділено в буфері.
Небезпечний приклад:
char name[8];
scanf("%s", name);
Безпечніше обмежити розмір:
char name[8];
scanf("%7s", name);
Небезпека: buffer overflow може спричинити аварійне завершення програми, пошкодження пам’яті або security vulnerability.
Static analysis
Static analysis — це аналіз коду без запуску програми.
Інструменти можуть знаходити:
- potential null dereference;
- memory leaks;
- buffer overflows;
- uninitialized variables;
- unreachable code;
- suspicious casts;
- format string issues;
- undefined behavior risks.
Приклади інструментів:
- Clang Static Analyzer;
- cppcheck;
- Coverity;
- PVS-Studio;
- GCC warnings;
- clang-tidy.
Практична роль: static analysis допомагає знаходити помилки до запуску програми й до потрапляння коду в production.
Sanitizers
Sanitizers — це інструменти runtime-перевірки.
Поширені sanitizers:
- AddressSanitizer;
- UndefinedBehaviorSanitizer;
- ThreadSanitizer;
- MemorySanitizer;
- LeakSanitizer.
Приклад:
clang -fsanitize=address -g main.c -o app
./app
Практична користь: sanitizers допомагають швидко знаходити memory bugs, які важко помітити вручну.
Makefile
Makefile використовується для автоматизації збірки.
Приклад:
CC = gcc
CFLAGS = -Wall -Wextra -std=c11
app: main.o utils.o
$(CC) $(CFLAGS) main.o utils.o -o app
main.o: main.c utils.h
$(CC) $(CFLAGS) -c main.c
utils.o: utils.c utils.h
$(CC) $(CFLAGS) -c utils.c
clean:
rm -f *.o app
Практична роль: Makefile дозволяє не вводити довгі команди компіляції вручну щоразу.
CMake
CMake — популярна система генерації build-файлів для C і C++ проєктів.
Приклад `CMakeLists.txt`:
cmake_minimum_required(VERSION 3.16)
project(MyApp C)
set(CMAKE_C_STANDARD 11)
add_executable(myapp main.c utils.c)
Практична роль: CMake часто використовують у більших C/C++ проєктах, де потрібна переносима система збірки.
Embedded C
Embedded C — це використання C для мікроконтролерів і вбудованих систем.
C у embedded популярна, тому що дає:
- контроль пам’яті;
- контроль регістрів;
- мінімальний runtime;
- швидкість;
- передбачуваність;
- доступ до hardware;
- можливість писати firmware;
- підтримку cross-compilation.
Сценарії:
- мікроконтролери;
- сенсори;
- побутова техніка;
- автомобільні системи;
- IoT;
- industrial controllers;
- medical devices;
- embedded Linux.
Перевага в embedded: C дозволяє писати ефективний код навіть для пристроїв із дуже обмеженою пам’яттю й процесором.
Системне програмування
C є класичною мовою системного програмування.
Вона використовується для:
- операційних систем;
- драйверів;
- файлових систем;
- мережевих стеків;
- компіляторів;
- runtime libraries;
- системних утиліт;
- shell tools;
- low-level APIs;
- embedded firmware.
Суть системного програмування: C дозволяє працювати близько до операційної системи, пам’яті, процесів і апаратного забезпечення.
C і Linux
C має особливе значення для Linux-екосистеми.
У C написано багато системних компонентів, зокрема:
- ядро Linux;
- системні утиліти;
- бібліотеки;
- драйвери;
- low-level daemons;
- частина інфраструктурного ПЗ.
Важливо: знання C допомагає краще розуміти Unix/Linux, системні виклики, пам’ять, процеси й низькорівневі API.
C і C++
C і C++ пов’язані, але це різні мови.
| Критерій | C | C++ |
|---|---|---|
| Парадигма | Процедурна, низькорівнева | Multi-paradigm: procedural, object-oriented, generic |
| Абстракції | Мінімальні | Класи, шаблони, RAII, STL |
| Runtime | Дуже компактний | Може бути складнішим |
| Використання | Системи, embedded, kernels, libraries | Застосунки, ігри, системи, high-performance software |
| Сумісність | Близька історично | Не є просто “C з класами” у сучасному вигляді |
Висновок: C++ виріс із C, але сучасний C++ має іншу культуру, інструменти й підхід до безпеки ресурсів.
C і Rust
C часто порівнюють із Rust, тому що обидві мови використовуються для системного програмування.
| Критерій | C | Rust |
|---|---|---|
| Контроль пам’яті | Ручний | Контроль через ownership і borrow checker |
| Безпека | Залежить від дисципліни програміста | Більше перевірок на етапі компіляції |
| Простота мови | Синтаксис відносно компактний | Більше концепцій для вивчення |
| Legacy | Дуже велика кодова база | Молодша екосистема |
| Використання | Embedded, OS, libraries, drivers | Systems, services, security-sensitive software |
Висновок: C дає простий і прямий контроль, а Rust намагається зберегти продуктивність системного рівня з сильнішими гарантіями безпеки пам’яті.
C і Python
C і Python часто використовуються разом, але мають різні ролі.
| Критерій | C | Python |
|---|---|---|
| Виконання | Компіляція | Інтерпретація або bytecode/runtime |
| Швидкість | Дуже висока | Зазвичай нижча для CPU-bound коду |
| Рівень | Низькорівневий | Високорівневий |
| Пам’ять | Ручне керування | Автоматичне керування |
| Типові задачі | Системне ПЗ, embedded, бібліотеки | Скрипти, automation, data science, web, AI |
Практичний висновок: Python зручний для швидкої розробки, а C — для продуктивних низькорівневих частин, бібліотек і системних компонентів.
Переваги C
Основні переваги мови C:
- висока продуктивність;
- контроль пам’яті;
- компактний runtime;
- близькість до hardware;
- переносимість;
- велика кількість компіляторів;
- зрілість екосистеми;
- використання в системному програмуванні;
- придатність для embedded;
- велика legacy-база;
- простий ABI;
- зручність для бібліотек і runtime-компонентів.
Головна перевага: C дає максимальний контроль і високу ефективність при відносно простій моделі мови.
Обмеження C
C має серйозні обмеження.
Основні проблеми:
- ручне керування пам’яттю;
- ризик buffer overflow;
- undefined behavior;
- відсутність автоматичної перевірки меж масивів;
- складність безпечної роботи з рядками;
- немає вбудованих високорівневих структур даних;
- складність великих codebase;
- небезпечні casts;
- помилки pointer arithmetic;
- відсутність вбудованих exceptions;
- багато відповідальності на програмісті.
Помилка: вважати C простою мовою лише через невеликий синтаксис. Складність C полягає в пам’яті, ресурсах, undefined behavior і безпеці.
Коли варто використовувати C
C доцільно використовувати, коли потрібні:
- низькорівневий контроль;
- висока продуктивність;
- embedded;
- мінімальний runtime;
- прямий доступ до hardware;
- системне програмування;
- бібліотеки для інших мов;
- drivers;
- kernels;
- resource-constrained systems;
- interoperability через C ABI.
Практична порада: C варто обирати тоді, коли контроль ресурсів важливіший за швидкість розробки й автоматичну безпеку.
Коли C може бути невдалим вибором
C може бути не найкращим вибором для:
- швидкого web development;
- business applications;
- CRUD-систем;
- прототипів;
- enterprise UI;
- складної бізнес-логіки без потреби в низькому рівні;
- команд без досвіду memory safety;
- застосунків, де безпека важливіша за legacy-сумісність;
- сценаріїв, де Python, Java, Go, Rust або C# дають швидшу розробку.
Важливо: C не потрібно використовувати всюди. Це сильний інструмент для конкретних задач, а не універсальна відповідь на всі проблеми.
Стиль коду
Хороший C-код має бути:
- простим;
- явним;
- добре структурованим;
- із перевіркою помилок;
- із зрозумілими іменами;
- із мінімумом глобальних змінних;
- із чітким ownership пам’яті;
- із документацією API;
- із тестами;
- із static analysis;
- із попередженнями компілятора.
Приклад корисних прапорців:
gcc -Wall -Wextra -Wpedantic -std=c11 main.c -o app
Головне правило стилю: у C краще писати трохи більше явного коду, ніж приховувати складну поведінку в макросах і неочевидних pointer-трюках.
Тестування C-коду
C-код потрібно тестувати, особливо якщо він працює з пам’яттю, файлами, мережею або hardware.
Підходи:
- unit tests;
- integration tests;
- fuzz testing;
- static analysis;
- sanitizers;
- valgrind;
- code review;
- regression tests;
- boundary tests;
- failure tests.
Практична роль: тестування в C особливо важливе, тому що багато помилок не ловляться автоматично мовою.
Fuzz testing
Fuzz testing — це тестування випадковими, напіввипадковими або згенерованими input-даними.
Fuzzing допомагає знаходити:
- crashes;
- buffer overflows;
- parsing bugs;
- memory errors;
- unexpected states;
- security vulnerabilities.
Інструменти:
- AFL++;
- libFuzzer;
- honggfuzz;
- OSS-Fuzz.
Практична порада: fuzz testing особливо корисний для C-коду, який парсить файли, мережеві пакети або зовнішній input.
Хороші практики C
Рекомендовано:
- завжди перевіряти результат `malloc`;
- звільняти пам’ять через `free`;
- після `free` ставити pointer у `NULL`;
- перевіряти межі масивів;
- уникати небезпечних string functions;
- використовувати `sizeof` правильно;
- вмикати compiler warnings;
- використовувати sanitizers;
- писати tests;
- уникати глобального mutable state;
- документувати ownership;
- мінімізувати макроси;
- перевіряти return codes;
- робити code review.
Головне правило: безпечний C-код — це результат дисципліни, перевірок, тестів і явного керування ресурсами.
Типові помилки початківців
Поширені помилки:
- неініціалізовані змінні;
- вихід за межі масиву;
- забутий `free`;
- double free;
- use-after-free;
- неправильний `sizeof`;
- неправильний формат у `printf`;
- розіменування `NULL`;
- плутанина між `=` і `==`;
- повернення pointer на локальну змінну;
- відсутність `break` у `switch`;
- неправильна робота з рядками;
- ігнорування warnings компілятора.
Небезпека: багато помилок у C не завжди проявляються одразу. Програма може працювати під час тесту, але падати в production.
Приклади задач на C
Обчислення суми масиву
#include <stdio.h>
int sum_array(const int *items, int count) {
int sum = 0;
for (int i = 0; i < count; i++) {
sum += items[i];
}
return sum;
}
int main(void) {
int numbers[] = {1, 2, 3, 4, 5};
int result = sum_array(numbers, 5);
printf("Sum: %d\n", result);
return 0;
}
Безпечніше копіювання рядка
#include <stdio.h>
#include <string.h>
int main(void) {
char destination[16];
const char *source = "Hello";
snprintf(destination, sizeof(destination), "%s", source);
printf("%s\n", destination);
return 0;
}
Структура користувача
#include <stdio.h>
typedef struct {
int id;
char name[50];
} User;
void print_user(const User *user) {
if (user == NULL) {
return;
}
printf("User #%d: %s\n", user->id, user->name);
}
int main(void) {
User user = {1, "Alice"};
print_user(&user);
return 0;
}
Підказка: у прикладах C важливо звертати увагу не лише на результат, а й на перевірки, розміри буферів і ownership пам’яті.
Джерела
- ISO C standards.
- The C Programming Language, Brian W. Kernighan and Dennis M. Ritchie.
- Документація GCC.
- Документація Clang/LLVM.
- Документація GNU Make.
- Документація CMake.
- CERT C Coding Standard.
- Документація cppcheck, Clang Static Analyzer, sanitizers і Valgrind.
- Документація стандартної бібліотеки C.
Висновок
Мова програмування C — це фундаментальна компільована мова для системного, embedded і високопродуктивного програмування. Вона дає прямий контроль над пам’яттю, ефективність, переносимість і доступ до низькорівневих механізмів.
C залишається важливою мовою для операційних систем, драйверів, embedded-систем, компіляторів, runtime-бібліотек і продуктивних компонентів. Водночас C потребує високої дисципліни: ручне керування пам’яттю, вказівники, undefined behavior і відсутність автоматичних перевірок можуть створювати серйозні помилки й security-ризики.
Головна думка: C — це мова контролю, продуктивності й системного рівня. Вона дуже потужна, але вимагає уважності, тестування, перевірки пам’яті й відповідального стилю програмування.
Див. також
- Програмування
- Мова програмування
- Системне програмування
- Низькорівневе програмування
- Компілятор
- GCC
- Clang
- CMake
- Makefile
- Вказівник
- Пам’ять
- Embedded C
- Linux
- Операційна система
- C++
- Rust
- Python
- Налагодження коду
- Логування
- Безпека застосунків
- Алгоритм
- Структура даних