Суброзділ
Про мене
ХТО я такий?
Хто я такий? Для вас я ще один чувак з інтернету, який любить писати, програмувати та робити різні речі. Я не ставлю перед собою мету комусь і щось донести через мережу. Кому потрібно сам все знайде. Інтернет відкриває широкі можливості не тільки, щоб гаяти час у соціальних мережах, а й самоудосконалюватись.
Не так давно я більш інтенсивно почав цікавитись програмуванням та операційними системами Linux, і мікроконтролерами. Все що люблю я буду розміщувати тут. Все що хоч раз мені допомогло буду тут записувати на цих сторнінках.
Це мої нотатки на полях.
Це все, що я можу зараз придумати. Сподіваюся, я додам до цього ще щось пізніше. :sweat_smile:
Вам цікаво про мене? Використовуйте лінки нижче, щоб відвідати мої профілі.
SDL2
Суброзділ SDL2
Заняття №1
01_hello_SDL
01_hello_SDL.cpp
/**
* @file 01_hello_SDL.cpp
* @author Sam4uk (sam4uk.site@gmail.com)
* @date 2025-05-03
*
* @copyright Copyright © Sam4uk 2025 (Sam4uk.site@gmail.com)
*
*/
#if defined(__linux)
#include <SDL2/SDL.h>
#elif defined(WIN32)
#include <SDL.h>
#endif
#include <exception>
#include <iostream>
#include <memory>
#include <stdexcept>
/** Screen dimension constants */
const int SCREEN_WIDTH{640}, SCREEN_HEIGHT{480};
int main(int argc, char* args[]) try {
/** Initialize SDL */
struct SDL_Guard {
SDL_Guard(Uint32 flags) {
if (0 > SDL_Init(flags))
throw std::runtime_error("SDL could not initialize! SDL_Error:" +
std::string(SDL_GetError()));
}
~SDL_Guard() { SDL_Quit(); }
} sdl_guart(SDL_INIT_VIDEO);
/** The window we'll be rendering to */
std::unique_ptr<SDL_Window, void (*)(SDL_Window*)> window{
SDL_CreateWindow("C++ SDL blank by Sam4uk", SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT,
SDL_WINDOW_SHOWN),
[](SDL_Window* w) {
if (w) SDL_DestroyWindow(w);
}};
if (!window)
throw std::runtime_error("Window could not be created! SDL_Error:\n" +
std::string(SDL_GetError()));
/** The surface contained by the window */
SDL_Surface* screenSurface{SDL_GetWindowSurface(window.get())};
/** Fill the surface white */
SDL_FillRect(screenSurface, NULL,
SDL_MapRGB(screenSurface->format, 0xFF, 0xFF, 0xFF));
SDL_Event e;
bool quit{false};
do {
// Update the surface
SDL_UpdateWindowSurface(window.get());
while (SDL_PollEvent(&e)) {
switch (e.type) {
case SDL_QUIT:
quit = true;
break;
default:
break;
}
}
} while (!quit);
return EXIT_SUCCESS;
}
catch (const std::exception& e) {
std::cout << e.what() << std::endl;
return EXIT_FAILURE;
}
catch (...) {
std::cout << "Something wrong\n";
return -EXIT_FAILURE;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(01_hello_SDL)
add_executable(${PROJECT_NAME} 01_hello_SDL.cpp)
target_link_libraries(${PROJECT_NAME} SDL2)
Збирання проекту
cmake -B build -G="Unix Makefiles"
cmake --build ./build -j$(nproc)
GNU/Linux
РОЗДІЛ З НОТАТКАМИ ПРО ЛІНУКС
Суброзділ GNU/Linux
Bash yaml parser
yb - bash парсер YAML
yb
надає чисте Bash рішення для парсингу YAML.
Використання
yb [-h|-a|-c|-q|-r|-A-C-d-F-K-l-L-R-n-T] [-f <файл>|-o <об'єкт>] [-k <ключ>] [-v <значення>|-O <значення об'єкта>]
Приклади
./yb -f файл.yaml -k "ключ.дочірнійключ"
З репозиторію ви можете швидко спробувати так:
./yb -f tests/user.yaml -k "yb"
Створення ключів:
./yb -af tests/user.yaml -k "new.key"
додати вбудоване значення:
./yb -af tests/user.yaml -k "new.key" -v "one, two"
змінити його:
./yb -cf tests/user.yaml -k "new.key" -v "three"
додати значення списку:
./yb -af tests/user.yaml -k "new.- list" -v "- one - two"
додати ASCII всередині ключа каналу:
./yb -af tests/user.yaml -k "new.ascii|" -v "|> ___ _ |> \ \// |> \ / |> / / |> /_/"
Маніпулювати YAML як змінною bash:
# Рекомендується використовувати опцію '-R' raw для отримання вмісту без колірних кодів
my_YAML=$(./yb -f tests/user.yaml -k "new")
my_YAML=$(./yb -ao "${my_YAML}" -k "in_memory" -v "true")
Додати його до файлу:
./yb -af tests/user.yaml -k "new.copy" -o "${my_YAML}"
Видалити одне значення:
./yb -rf tests/user.yaml -k "new.- list" -v "- one"
або видалити все:
./yb -rf tests/user.yaml -k "new"
Встановлення
З репозиторію
З репозиторію
git clone https://github.com/t0pd4wn/yb.git
cd yb
chmod +x yb
git clone https://github.com/Sam4uk/yb.git
cd yb
chmod +x yb
yb
можна використовувати безпосередньо з папки репозиторію або скопіювати та використовувати як окремий файл у проекті.
З URL-адреси
Ви можете використовувати цю команду для безпосереднього завантаження скрипта yb
, де це необхідно:
З URL-адреси
bash <(echo "https://gitlab.com/t0pd4wn/yb/-/raw/main/yb"|(read l; wget $l || curl $l >yb)) && chmod +x yb;
bash <(echo "http://sam4uk.github.io/yb/yb"|(read l; wget $l || curl $l >yb)) && chmod +x yb;
Якщо ви хочете, щоб yb
був доступний для всієї системи, виконайте цю команду з папки репозиторію:
sudo cp yb /usr/local/bin/
Однорядковий код
Ви можете використовувати цю команду для завантаження yb
у поточну папку одним рядком:
Однорядковий код
bash <(echo "https://gitlab.com/t0pd4wn/yb/-/raw/main/yb"|(read l; wget $l || curl $l >yb)) && chmod +x yb;
bash <(echo "http://sam4uk.github.io/yb/yb"|(read l; wget $l || curl $l >yb)) && chmod +x yb;
Ви можете використати цю команду для встановлення yb
у вашу систему одним рядком:
Однорядковий код
bash <(echo "https://gitlab.com/t0pd4wn/yb/-/raw/main/yb"|(read l; wget $l || curl $l >yb)) && chmod +x yb && sudo cp yb /usr/local/bin && rm yb;
bash <(echo "http://sam4uk.github.io/yb/yb"|(read l; wget $l || curl $l >yb)) && chmod +x yb && sudo cp yb /usr/local/bin && rm yb;
There may be pirates
У свій форк я додав можливість завантаження парсеру у форматі deb пакету який можна завантажити за цим посиланням
Параметри
Параметри yb
поділяються на 3 типи:
action
: параметри дії виконуються з файлом і не сумісні один з одним. Вони сумісні зinput
, але не зformat
.input
: параметри введення є параметрами, що встановлюються користувачем, і сумісні один з одним. Вони сумісні як зaction
, так і зformat
.input
: параметри форматування виводять вивід різними способами. Вони сумісні один з одним, з типомinput
, але не з типомaction
.
Опція | Ім’я | Тип | Опис | Приклад | Примітки |
---|---|---|---|---|---|
-a | add | action | Додає ключ(и), значення(я) або обидва. | yb -f "file.yaml" -a -k "key" -v "value" | |
-c | change | action | Змінює значення(я). | yb -f "file.yaml" -c -k "key" -v "new_value" | |
-q | query | action | Виводить true або false , якщо ключ(и), значення(я) або обидва присутні чи ні. | yb -f "file.yaml" -q -k "key" | Для отримання значення каналу -v 'значення каналу' рекомендується використовувати одинарні лапки. |
-r | remove | action | Видаляє ключ(и), значення(я) або обидва. | yb -f "файл.yaml" -r -k "ключ" -v "значення" | Для видалення значення каналу -v 'значення каналу' рекомендується використовувати одинарні лапки. |
-f | file | input | шлях до файлу YAML. | yb -f "файл.yaml" | Файл можна представити без опції -f , як опцію $1 . -f та -c несумісні один з одним. |
-o | object | input | об’єкт YAML. | yb -o "${YAML_object}" | Об’єкт YAML можна використовувати з усіма діями. -f та -o сумісні між собою, лише під час додавання об’єкта до файлу. |
-O | object value | input | значення об’єкта YAML. | yb -f "file.yaml" -O "${YAML_object}" | Об’єкт YAML можна використовувати з дією -a . |
-k | key | input | Шлях вибору ключів. | yb -f "file.yaml" -k "ключ" | Підтримує ключі у форматі:key , key.childkey , - list-key , pipe-key| . Можна вказати кілька ключів з роздільником . . |
-v | value | input | Значення, які потрібно додати, видалити, запитати або змінити. | yb -f "file.yaml" -k "ключ" -v "значення" | Підтримує значення у форматі: value , - list-value , |> pipe-value . |
-A | array | формат | Виводить вивід у вигляді масиву bash. | yb -f "file.yaml" -A -k "key" | Забезпечить інше форматування, якщо використовувати з -F або -d . |
-C | colors | format | Примусово використовує кольори у нетермінальному виводі. | yb -Cf "file.yaml" -A -k "key" | |
-d | depth | format | Забезпечує вихід з оригінальною глибиною. | yb -f "file.yaml" -d -k "key.childkey" -v "new_value" | |
-F | format | format | Друкує відформатований вивід для представлення деревоподібності в рядку. | yb -f "file.yaml" -F -k "key" | Забезпечить інше форматування, якщо використовувати з -A або -d . |
-K | keys ключі | format | Друкує лише ключі. | yb -Kf "file.yaml" -k "key" | |
-l | line | format | Друкує { {line}} у кожному рядку. | yb -f "file.yaml" -l -k "key" | |
-L | level | format | Друкує { {<номер рівня>} } у кожному рядку. | yb -f "file.yaml" -L -k "key" | |
-R | raw | format | Друкує вивід без доданих кольорів, коментарів та порожніх рядків. | yb -f "file.yaml" -R -k "key" | |
-n | number | format | Друкує { {<номер рядка>} } на кожному рядку. | yb -f "file.yaml" -n -k "key" | |
-T | type | format | Друкує тип значення. | yb -f "file.yaml" -T -k "key" | Підтримує null , boolean , integer , floating number , string . |
-s | spaces | Deprecated | Вибір кількості пробілів. |
Підтримка YAML
yb
забезпечує базову підтримку YAML для редагування та читання YAML-файлу. Починаючи з версії 0.9, yb
не підтримує розширені функції, такі як пошук на основі груп, прив’язки, псевдоніми та перевизначення.
Розробка
Повні вихідні коди доступні в папці /src/
. Версія, що знаходиться на кореневому рівні, зібрана за допомогою скрипта /src/tools/dist.sh
.
Тести
Простий набір тестів доступний у папці tests/
. Він представляє різні варіанти використання yb
.
Щоб запустити його, виконайте команду нижче з кореневого рівня репозиторію:
./tests/tests.sh
Ви можете надати додатковий параметр для вибору парсера (наприклад, під час розробки):
./tests/tests.sh "./src/yb.dev"
Дякую
Всім вам, YAML-івцям!
Зроблено t0pd4wn на Bash.
Other
Суброзділ Other
videotestsrc
gst-launch-1.0 videotestsrc pattern=snow ! autovideosink
smpte (0)
snow (1)
black (2)
white (3)
red (4)
green (5)
blue (6)
checkers-1 (7)
checkers-2 (8)
checkers-4 (9)
checkers-8 (10)
circular (11)
blink (12)
smpte75 (13)
zone-plate (14)
gamut (15)
chroma-zone-plate (16)
solid-color (17)
ball (18)
smpte100 (19)
bar (20)
pinwheel (21)
spokes (22)
gradient (23)
colors (24)
smpte-rp-219 (25)
Libraries
Суброзділ Libraries
WD.Easy
WD.Easy
Ця бібліотека призначена для керування режимами роботи сторожового таймера, поки тільки мікроконтролерів серії AVR
.
Особливості
Я не писав її в стилі класу синглтон (сторожовий таймер то один). Все рівно скільки об’єктів не було б створено всі вони працюватимуть з одним регістом. Перед собою поставив завдання, щоб бібліотека була максимально легкою та можна використовувати для будь якої ардуїни та не тільки.
Основна вимога щоб ТАЙМЕР МАЄ ПОЧАТИ ВІДЛІК ЩЕ ДО СТАРТУ ФУНКЦІЇ setup
, а ще краще до початку main
.
Здається вийшло
Методи доступні у таймеру
Метод | ОПИС | результат |
---|---|---|
setTimeOut(uint8_t) | Задає інтервал спрацьовувань таймеру | void |
setMode(uint8_t) | Задає дію при спрацьовуванні таймеру | void |
setTask(ptr*) | Задати функцію обробник переривання | void |
getTimeOut() | Отримати періодичність спрацьовуань | uint8_t |
getMode() | Отримати режим роботи таймеру | uint8_t |
isEnable() | Чи активний таймер | bool |
reset() | Почати новий відлік | void |
Інтервали
У таблиці наведені параметри які приймає метод setTimeOut(uint8_t)
та повертає getTimeOut()
.
define | час c | |
---|---|---|
WD_15MS | 0.015 | |
WD_30MS | 0.030 | |
WD_60MS | 0.060 | |
WD_120MS | 0.120 | |
WD_250MS | 0.250 | |
WD_500MS | 0.500 | |
WD_1S | 1 | |
WD_2S | 2 | |
WD_4S | 4 | |
WD_8S | 8 |
Примітка Час приблизний і не підходить для вимірювання точних інтервалів, оскільки сторожевий таймер тактується від внутрішнього низькочатоного генератора, який “пливе” при зміні напруги живлення та температури
Дії
define | Означення |
---|---|
DISABLED | Таймер зупинено |
INTERRUPT | При спрацьовуванні буде викликано обробник переривання |
SYSTEMRESET | При спрацьовуванні буде виконано перезавантаження мікроконтроллера |
INTERUPTANDRESET | При спрацьовуванні буде викликано обробник переривання через такий же інтервали виконається перезавантаження контролеру |
Обробник переривань
За замовчуванням обробникпереривань не вказаний (nullptr
).
Приклади
Приклад перший
void WD_tick() {
Serial.print("WD_tick()");
Serial.println(millis());
}
WatchDogEasy WD(WatchDogEasy::WD_1S, WatchDogEasy::INTERRUPT, WD_tick);
void setup() { Serial.begin(115200); }
void loop() {}
Тут ми ініціюємо таймер на спрацьовування кожну секунду, при спрацьовуванні генеруватиметься переривання, яке буде оброблятися функцією WD_tick()
.
В цьому прикладі таймер починає відлік ще до початку роботи функції setup()
. Це зручно якщо при ініціації контролеру є ризик завиcнути
Приклад другий
void WD_tick() {
Serial.print("WD_tick()");
Serial.println(millis());
}
WatchDogEasy WD;
void setup() {
Serial.begin(115200);
WD.setTimeOut(WatchDogEasy::WD_1S);
WD.setMode(WatchDogEasy::INTERRUPT);
WD.setTask(WD_tick);
}
void loop() {}
Тут все те саме, але таймер починає відлік після останнього сеттера.
Всі сетери скидають таймер на новий відлік. Геттери цього не роблять (Будьте уважні). Під час виконання геттеру може збігти час і виконатися дія запрограмованна у таймері.
Приклад третій
WatchDogEasy WD(WatchDogEasy::WD_1S, WatchDogEasy::SYSTEMRESET, nullptr);
int main(){
for(;;){
}
}
Тут таймер ініціалізується ще до входження у main()
. На мій погляд це супер зручно
“Системні вимоги”
Таймер працюватиме на будь якому мікроконтролери серії Ардуїно та AVR
Місця в флеш займе додаткових 198 байт та 2 байти в оперативній пам’яті
Найголовніше
Встановлення
- Можна завантажити zip архів WD.Easy v1.0 І розпакувати вміст у потрібний каталог
- Бібліотека доступна для встановлення через менеджер бібліотек Arduino IDE. Шукайте в категрії
- Можна завантажити zip архів WD.Easy prerelise. Це гілка з тестовим не відлагодженим та найсвіжішім кодом. Встанволюється як перший пункт даного списку
Потрібно підключити
#include <WD.Easy.hpp>
та не забувати скадати таймер, щоб він не скинув програму
CRSF
/**
* Для того щоб почативикористовувати мінімальний функціонал бібліотеки
* Потрібно її заінклюдити
*/
#include <CrsfSerial.h>
/**
* В даному прикладі розглядається підключення приймача CRSF до Serial3
* Arduino Mega 2560 або до Serial якщо у вас інша "доска".
*
* Будь ласка не використовуйте програмні реалізації серійного порту.
* Використовуйте тільки апаратні серійні інтерфейси оскільки протокол
* CRSF використовує нестандартну швидкість передачі даних яка визначена
* в константі CRSF_BAUDRATE. Якщо користуєтесь редактором Arduino IDE
* то ви не знайдете у моніторі серійного порту значення швидкості 420000
* boud.
*
* По друге. Переконайтеся що ваша плата може працювати на такій швидкості
* послідовного інтерфейсу. Ваш мікроеонтролер можливо не може працювати на
* такій високій частоті через апаратні обмеження (не достатня частота тактового
* генератора), софтверні обмеження (Не можливість вказати нестандартну швидкість)
*/
#if defined(__AVR_ATmega2560__)
/**
* Тут ми створюємо об'єкт crsf класу CrsfSerial, який слухатиме Serial3.
*
* В конструкторі можна задати іншу швидкість роботи, але для цього потрібно
* переналаштовувати приймач, якщо він підтримує таку кастомізацію. Перевірте
* ваш модуль на наявність такої опції
*/
CrsfSerial crsf(Serial3, CRSF_BAUDRATE);
#else
/**
* Для плат які зібрані не на ATmega2560
*/
// CrsfSerial crsf(Serial, CRSF_BAUDRATE); ///< Цей
// CrsfSerial crsf(Serial); ///< і цей записи тотожні
/// за замовчуванням crsf працює
/// на швидкості 420000 бод
#error NOT MEGA2560
#endif
void packetChannels(); ///< Тут ми оголошуємо функцію яку реалізуємо пізніше
/// Ця функція буде виконуватися коли на порт "прилетить"
/// пакет з командами
/**
* Цей приклад написано для Ардуїно Мега і призначений для тестування пульта та
* роботи скетчу.
*
* Скетч очікує що модуль приймача вісить на Serial3 а вся інформація для відладки
* вилітає у стандартний серійний порт у монітор порта
*/
void setup() {
Serial.begin(115200); ///< Ініціюємо черійний порт для відладкт
crsf.begin(); ///< Починаємо слухати приймач
crsf.onPacketChannels = &packetChannels; ///< Вказуємо яка функція "витягає" команди
}
void loop() { crsf.loop(); } ///< Тут живе програма
void packetChannels() { ///< Функція яка обробляє пакет
for (auto ch{1}; ch <= CRSF_NUM_CHANNELS; ++ch) { ///< Проходимо по всіх каналах
Serial.print(crsf.getChannel(ch)); ///< Друкуємо
if (ch != CRSF_NUM_CHANNELS) Serial.print(", "); ///< Ствимо розділові знаки
} ///< Повторюємо
Serial.println(); ///< Починаємо з нового рядка
}
Деталі
При виявленні неточностей чи помилок буду вдячний за фітбек
C/C++
Суброзділ C/C++
MY NAME IS main
INTRO
Мене засміяли коли я поцікавився які форми декларації головної функції main
допустимі.
Ніхто мені не дав відповіді, яку я хотів отримати. Тож це мої нотатки про головну функцію
My name is main
main
return
В пошуках інформації про функцію main
мені на форумах та в групах понакидали лінки на різні статті в яких писалося про головну функцію програми. Так от в одній з цих статей автор писав, що писати у функціях які повертають void
писати return
не обов’язково. Дозволю собі не погодитися з автором. Писати це ключове слово потрібно і бажано.
return;
Писати чи не писати return
у void
функціях? Візьмемо для прикладу дві реалізації однієї і тієї ж функції. Одні з ключовим словом
void F(void){
// ...
// багато коду
// ...
return;
}
, а інша без
void F(void){
// ...
// багато коду
// ...
}
Якщо потікфункції лінійний то між функціями нема ніякої різниці. І компілятор навідь ворнінга не викине. Так як у void
функціях компілятор самостійно робить вихід з return
перед закриваючою скобкою }
, яка позначає кінець тіла функції.
Але якщо логіка функції не є лінійною то за допомогою ключового слова return
ми можемо достроково завершити роботу функції, якщо буде виконана чи не виконана якась умова.
void F(void){
// підготовка
// код
if(!ready)
return; // не треба продовжувати роботи
// ...
// багато коду
// ...
return; // успішне завершення
}
Я, наприклад, прихильник писати return
у void
функціях і можу не використати це ключове слово лише коли конкретно профілонив (не захтів писати).
Але як дізнатись чи успішно завершена функція чи аварійно? Потрібно якийсь результат отримати від її роботи
return 0;
Який результат має повертати int main(void)
?
Згідно стандарту нуль вказує на успішене виконання функції. У бібліотеці є визначений макрос який позначає EXIT_SUCCESS
успішну роботу. Цей макрос рівний 0
.
#include <stdlib.h>
int main(void){
// ...
// багато коду
// ...
return EXIT_SUCCESS; // успішне завершення
}
що тотожно
#include <stdlib.h>
int main(void){
// ...
// багато коду
// ...
return 0; // успішне завершення
}
А яке чило повертати, якщо було аварійне завершення програми? Стандартно повертають 1
.
#include <stdlib.h>
int main(void){
// ...
// багато коду
// ...
if(error)
return EXIT_FAILURE;
// ...
// багато коду
// ...
return EXIT_SUCCESS; // успішне завершення
}
це ж те саме
#include <stdlib.h>
int main(void){
// ...
// багато коду
// ...
if(error)
return 1;
// ...
// багато коду
// ...
return 0; // успішне завершення
}
Але ніде не написано, що обов’язково то має бути одиничка. Нам доступний широкий діапазон
min | max | bit |
---|---|---|
-32768 | 32767 | 16 |
-2,147,483,648 | 2,147,483,647 | 32 |
-9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 | 64 |
Все окрім 0
Таким чином ми можемо передавати повідомлення для скрипта, що викликатиме нашу програму, для прикладу, і сповіщатимемо, як ми виконали роботу.
Код | Опис |
---|---|
0 | Немає помилок |
1 | Попередження (не фатальна помилка (и)).Наприклад, один або кілька файлів були заблоковані якоюсь іншою програмою, тому вони не стиснулися. |
2 | Критична помилка |
7 | Помилка командного рядка |
8 | Недостатьньо пам’яті для роботи |
255 | Користувач припинив процес |
Які можна ловити errorlevel
. Тому не обов’язково return 1;
можна писати будь яке число, але задокументувати чому таке, а не 1
чи -1
.
Ніби розібралися void
int
.
(void)
Ніби вже й розібралися який результат має повертати main
функція. Спробуємо розібратися, які параметри може отримувати головні функція.
Почнемо з найпростішого і будемо поступово підвищувати ставку.
Почнемо з int main(void)
Такий формат виклику не дозволяє налаштувати початкові дані роботи функції. Дані для обробки програма може отримувати в інтерактивному форматі під час робочого потоку. Влинути на режим роботи програми до її початку не можливо
int main(void){
return EXIT_SUCCESS; // успішне завершення
}
(int)
Увага
Цей варіант виклику допустимий, але компілятор викидає попередження, що потрібно перевірити кількість аргументів у функції main
.
int main(int count){
switch (count){
0:
// цей код ніколи не виконається так як не можливо запустити програму щоб count був рівним нулю!
break;
1:
// що робити при запуску без параметрів
break;
2:
// це робити коли параметр один
break;
3:
// ця дія якщо параметрів два
break;
// ...
break;
default:
// цей код запуститься в інших випадках
break;
}
return EXIT_SUCCESS;
}
Програма в такому вигляді працездатна, але дещо “кастрована”.
Ми може дізнатися кількість переданих у програму параметрів. Змінювати її поведінку у залежності від кількості аргументів, але всі ті аргументи… З програми дізнатися, що їй сказали нема можливості. Їй абсолютно “до лампочки” що написано у комнадному рядку.
На такий
myprog -h
, і такий
myprog --help
, і такий
myprog --version
програма реагуватиме однаково.
myprog foo bar
Програма може реагувати тільки на різну кількість параметрів. Це не найзручніший спосіб керування програмою.
Тож переходимо до наступного варіанту
(int, char**)
Наступна форма, яка є стандартизованою це
#include <stdlib.h>
int main(int int_value, char** char_pointer){
// ...
// багато коду
// ...
return 0; // успішне завершення
}
Перший параметр типу int
передає кількість переданих параметрів нашій програмі.
Цей параметр НІКОЛИ не може бути рівним нулю (0
). Змінна int_value
може мати значення в проміжку [1
…INT_MAX
].
Гадаю вам відомо, що в мовах C
та C++
, і в інших відомих мені мовах програмувння починають рахувати з нуля. Тобто під нульовим параметром ми маємо шлях до виконуваного вайлу.
А як ми можемо отримати значення передані програмі з командного рядка? Оскільки розглядаємо main
з двома параметрами то мабуть у другому параметрі. Другий параметр є вказівником на масив нультермінальних рядків з якого ми можемо прочитати всі параметри передані нашій програмі, першим елементом якого є шлях до виконуваного файлу.
#include <stdlib.h>
#include <stdio.h>
int main(int, char** pointer){
printf("My path: %s\n",*pointer);
return EXIT_SUCCESS; // успішне завершення
}
Цей коди виведе в консоль повідомлення з шляхом переданим у приграму при запиуску
./a.out
виведе:
My path: ./a.out
А
$(pwd)/a.out
виведе повний шлях до виконуваного файлу 1
/home/Sam4uk.site/main/a.out
#include <stdlib.h>
#include <cstdio>
int main(int, char** pointer){
for(auto it{pointer}; *it!=nullptr; it++)
if (it == argv)
printf("Programm path or param[0]: %s\n", *it);
else
printf("\tparam [%d]: %s\n", it - pointer,*it);
return EXIT_SUCCESS; // успішне завершення
}
Якщо запустити програму
./a.out one two foo bar end
в результаті її роботи буде виведено приблизно такий текст
Programm path or param[0]: ./a.out
param [1]: one
param [2]: two
param [3]: foo
param [4]: bar
param [5]: end
Тобто не обов’язково звертатись до першого пареметру в якому передано їх кількість аргументів командного рядка.
#include <stdlib.h>
#include <cstdio>
int main(int count, char** pointer){
for(auto it{0}; it!=count; it++)
if (0 == it)
printf("Programm path or param[0]: %s\n", *pointer);
else
printf("\tparam [%d]: %s\n", it, pointer[it]);
return EXIT_SUCCESS; // успішне завершення
}
Цей код тотожний попередньому (результат виконання однаковий).
Тепер програма може отримувати та реагувати на різні параметри, та різну їх кількість. Лишається лише їх розпарсити але це вже інша тема
(int, char**, char**)
Йдемо далі? Додамо ще один “документований” параметр?
Мені траплялися фрагменти кодів, де main
має три параметри. Саме це мене підштовхуло шукати всі можливі та умовно можливі варіанти функції.
Якщо попередній варіант int main(int, char**)
ми могли прочитати кількість c-string
з першого параметру, то наступний третій параметр… Щоб отримати кількість елементів нам потрібно “вручну” рахувати
#include <сstdlib>
#include <cstdio>
int main(int, char**, char** e){
int i{0};
for(auto c{e}; *c != nullptr; c++,i++)
printf("\t%s \n",*c);
printf("total: %d", i);
return EXIT_SUCCESS; // успішне завершення
}
На цьому ноуті, який я використовую цей код нарахував 81
рядок змінних
GJS_DEBUG_TOPICS=JS ERROR;JS LOG
LANGUAGE=uk_UA
USER=sam4uk.site
LC_TIME=uk_UA.UTF-8
# ......................................................
# ......................................................
TERM=xterm-256color
P9K_TTY=old
_P9K_TTY=/dev/pts/2
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
total: 81
Деякі рядки я видалив (Не потрібно всім знати, яке сміття в моїй системі. Це моє сміття).
З вихлопу зрозуміло, що це змінні оточення. Цікаво а в динаміці можна їх читати? Хочу підглянути адреси де знаходяться параметри.
#include <сstdlib>
#include <cstdio>
int main(int, char**, char** e){
printf("int @: %ld\nchar ** @:%ld\nchar ** @:%ld", &p1, p2, p3);
return EXIT_SUCCESS; // успішне завершення
}
І кілька разів поспіль запустимо виконуваний файл. Ми помітимо, що кожного разу адреси різні. Тож ми отримуємо копію змінних середовища на момент пуску програми. Від моменту пуску до моменту закінчення роботи програми ці значення можуть змінитися. Актуальні значення змінних середовища потрібно отримувти іншими методами.
(…)
int main(...){}
Я не знаю як з таким кодом працювати, але оце компілюється. Компілюється з ворнінгом, але ще й запускається.
Мені ще є що вчити. Одже продовження буде.
Ставлю трикрапик тут, а не в кінці
Замість резюме
Я довідався про стандартні main
и.
int main()
int main(void)
int main(int, char**)
int main(int, char**, char**)
І про (хочеться написати слово нестандартні, але за це можна й отрмати гнилими помідорами по пиці) ось такі варіанти. На ці варіанти компілятор матюкається, але все ж збирає життєздатні бінарні файли
int main(int)
int main(int, ...)
int main(int, char**, ...)
int main(int, char**, char**, ...)
Те що компілятор дозволяє “скріпя зубами” зібрати згадані мейни не значить що їх треба використовувати. Синтаксис мови дозволяє так писати, а бездушний компілятор попереджає, що за свої діяння він не несе відповідальності - його попросили так зробити.
Про імена
Історично склалось, що параметри функції main
мають наступні імена
# | name | type | |
---|---|---|---|
1. | argc | int | Кількість параметрів передених з командного рядка |
2. | argv | char** | Параметри передані через командний рядок |
3. | env | char** | Змінні оточнення |
Також дозволено використовувати фіктивну декларацію параметів (оголосити що функція отримуватиме параметр, але не оголосити ім’я змінної).
Про приклади
Не варто кидати помідорами та камінням за те як я написав приклади кодів. Десь я навмисне знущався зі стилю. Десь навмисне відходив від стандарту. Десь через неуважність. Десь через не знання. Я не намагаюсь когось навчити. Я сам вчусь.
Але якщо маєте слушні зауваження, я з радістю їх вислухаю.
BONUS
Коза
на всі випадки життя.
#include <iostream>
#include <cstdio>
void ParseCLI(int argc, char **argv){
return;
}
/** WINDOS */
#if defined(_WIN32)
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
/** Mac */
#elif defined(__APPLE__)
int main(int argc, char **argv)
/** Tux */
#elif defined(__linux)
int main(int argc, char **argv, char **env)
/** Other */
#else
int main(int argc, char **argv)
#endif
try {
#if defined(WIN32)
ParseCLI(__argc, __argv);
if (__argc == 0)
#else
ParseCLI(argc, argv);
if (argc == 0)
#endif
throw;
return EXIT_SUCCESS;
}
catch (...) {
std::cerr << "Oh, NOOOO!\n";
return EXIT_FAILURE;
}
Виведення дійсне для Linux систем ↩︎