Друкарня від WE.UA

Збираємо велосипед або пишемо власний printf

Один з небагатьох проектів, який мені справді було цікаво реалізовувати під час навчання в школі 42, - це написання власної імплементації функції printf.

printf - функція, що входить до бібліотеки stdio.h і використовується для виводу тексту до стандартного виводу. Імплементована у вигляді:

int printf ( const char * format, ... );

Приймає стрічку (const char *) і значення котрі ми хочемо додати до стрічки (…), повертає int зі значенням кількості надрукованих символів. Для форматування використовуються прапори у вигляді %d, %s, %c… Наприклад:

#include <stdio.h>

int main() {
    printf("Hello %s %c %d\n", "world",'!', 123);
    return 0;
}

Після компіляції і виконання виведе:

[username@:~]$ gcc hello_world.c
[username@:~]$ ./a.out
Hello world ! 123

Перейдемо до самого завдання проекту:

Опис

Написати бібліотеку з власною імлементацією функції printf

Функції дозволені для використання

malloc, free, write,
va_start, va_arg, va_copy, va_end

Підтримувані прапори

cspdiuxX%

В дозволених функціях бачимо write, власне цю функцію і будемо використовувати для виводу тексту до стандартного виводу.

#include <unistd.h>

ssize_t write(int fildes, const void *buf, size_t nbyte)

Функція write входить до бібліотеки unistd.h і використовується для виводу інформації до дескриптора файлу. В unix-подібних системах, дескриптор стандартного виводу дорівнює 1.

#include <unistd.h>

int main() {
    char c = '+';
// записуємо до stdout, 
// значення що знаходиться зі посиланням &c
// розміром 1 байт
    write(1, &c, 1); 

    return 0;
}

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

#include <unistd.h>

int myprintf(const char * str, ...){
    int counter = 0;
    // через цикл проходимо по стрічці,
    // поки не зустрінемо нул-термінатор -
    // '\0' значення котрим повинні закінчуватись всі стрічки в С
    while(str[counter] != '\0'){
        // виводимо по одному символу за крок циклу
        write(1, &str[counter], 1);
        counter++;
    }

    // повертаємо кількість надрукованих символів
    return counter;
}

int main() {
    char * str = "Hello world!";
    myprintf(str);
    
    return 0;
}

Компілюємо за допомогою gcc і виконуємо отриману програму:

[username@:~]$ gcc myprintf.c
[username@:~]$ ./a.out
Hello world!

Рядок з викликом функції write винесемо в окрему функцію printchar, котру потім будем вокористовувати як для простого виводу, так і обробки прапорів, отримаємо:

#include <unistd.h>

int printchar(char c){
    write(1, &c, 1);
    return 1;
}

int myprintf(const char * str, ...){
    int counter = 0;
    while(str[counter] != '\0'){
        printchar(str[counter]);
        counter++;
    }
    return counter;
}

int main() {
    char * str = "Hello world!";
    myprintf(str);
    
    return 0;
}

Тепер переходимо до найцікавішої частини - форматування виводу. Спробуємо обробити значення для вставки у вихідний рядок та найпростіший прапорець %c. Для цього внесемо зміни у функцію myprintf:

#include <unistd.h>

int printchar(char c){
    write(1, &c, 1);
    return 1;
}
// функція тепер приймає два аргементи
// стрічка з текстом і прапором котрий вказує 
// тип значення для вставки 
int myprintf(const char * str, char c){
    int counter = 0;
    while(str[counter] != '\0'){
        if (str[counter] == '%'){
            counter++;
            if (str[counter] == 'c'){
                printchar(c);
            }
        }
        else {
            printchar(str[counter]);
        }
        counter++;
    }
    return counter;
}

int main() {
    // використовуємо %с - прапор дл япозначення літери
    char * str = "Hello world!%c";
    myprintf(str, '+');
    
    return 0;
}

// Отримаємо:
// Hello world!+

Додамо обробку прапору стрічки - %s. Для цього напишемо функцію printstring.

int printstring(char *str){
    int len = str_len(str);
    
    write(1, str, len);
    
    return len;
}

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

int str_len(char * str){
    int counter = 0;
    while(str[counter] != '\0'){
        counter++;
    }
    
    return counter;
}

Перепишемо функцію myprintf так, щоб вона могла форматувати рядок, використовуючи прапорці %c та %s. Згідно з документацією, функція printf повинна повертати довжину виведеного тексту, крім самого виводу. Ми не можемо цього зробити, використовуючи лічильник (counter), оскільки довжина форматованого рядка може бути будь-якою, а лічильник відповідає лише за індексацію форматованого рядка. На щастя, у мові С стрічки це вказівники, тому ми можемо ітеруватись за вказівником (*str у нашому випадку).

#include <unistd.h>

int printchar(char c){
    write(1, &c, 1);
    return 1;
}

int str_len(char * str){
    int counter = 0;
    while(str[counter] != '\0'){
        counter++;
    }
    
    return counter;
}

int printstring(char *str){
    int len = str_len(str);
    
    write(1, str, len);
    
    return len;
}

int myprintf(const char * str, char c, char * s){
    int counter = 0;

    while(*str != '\0'){
        if (*str == '%'){
            str++;
            if (*str == 'c'){
                counter += printchar(c);
            }
            if (*str == 's'){
                counter += printstring(s);
            }
        }
        else {
            counter += printchar(*str);
        }
        str++;
    }
    
    return counter;
}

int main() {
    char * str = "%cello %s!";
    myprintf(str, 'H', "world");
    
    return 0;
}

// Отримаємо:
// Hello world!

Наша функція тепер може форматувати стрічку використовуючи по одному значенню типу char і char *, але нам потрібно підтримувати невизначену кількісь значень різних типів. Для цього подивимось на список дозволених для використання функцій і знайдемо там va_start, va_arg, va_copy, va_end - ці функції входять до бібліотеки stdarg.h. Вчергове перепишемо myprintf, тепер використовуючи va_ функції.

// підключаємо бібліотеку
#include <stdarg.h>
...
// використовуємо ... як аргумент функції
int myprintf(const char * str, ...){
    // створюємо список аргументів
    va_list args;
    int counter;
    
    // ініціалізуємо список аргументів
    // args - список аргументів
    // str - назва аргумента після котрого починається перелік args
    va_start(args, str);
    counter = 0;
    while(*str != '\0'){
        if (*str == '%'){
            str++;
            if (*str == 'c'){
                // передаємо аргумент приводячи до відповідного типу
                // передаємо аргумент як int
                // потім явно приводимо до типу char
                // не можемо одразу приводити до char
                // бо отримаємо попередження:
                // warning: 'char' is promoted to 'int' when passed through '...'
                counter += printchar((char)va_arg(args, int));
            }
            if (*str == 's'){
                // приводимо аргументдо типу char * 
                // і передаємо функції printstring
                counter += printstring(va_arg(args, char *));
            }
        }
        else {
            counter += printchar(*str);
        }
        str++;
    }
    // закінчуємо використання аргументів
    va_end(args);
    
    return counter;
}

Винесемо обробку прапорів в окрему функцію і додамо обробку ще одного прапору %%:

int handle_flag(va_list args, char flag)
{
    if (flag == 'c')
	return (printchar((char)va_arg(args, int)));
    if (flag == 's')
	return (printstring(va_arg(args, char *)));
    // прапор %% просто виводить %
    if (flag == '%')
	return (printchar('%'));
    return (0);
}

int myprintf(const char * str, ...){
    va_list args;
    int counter;
    
    va_start(args, str);
    counter = 0;
    while(*str != '\0'){
        if (*str == '%'){
            str++;
            counter += handle_flag(args, *str);
        }
        else {
            counter += printchar(*str);
        }
        str++;
    }
    va_end(args);
    
    return counter;
}

Таким чином ми почали створювати власний аналог функції printf. Описали обробку 3 прапорів з 9 (cspdiuxX%), створили базу для подальшого розширення функції. Наступним кроком буде імплементація обробки прапорів %i і %d, котрі будуть вимагати написання конвертеру типу int до стрічки.

P.S. код можна знайти за посиланням.

Статті про вітчизняний бізнес та цікавих людей:

  • Бронеплівка для вікон: коли вона доречна та як обрати рішення

    Як бронеплівка утримує уламки, де її встановлюють та чому перед монтажем важливо оцінити стан і конструкцію скління.

    Теми цього довгочиту:

    Бронеплівка На Вікна
  • Шлейф сучасного смартфону та його призначення

    Шлейф - це тонка пластикова стрічка з ледь видимими доріжками і саме вона зʼєднує важливі компоненти мобільного, без яких він не запрацює. Власники пристроїв Xiaomi, які стикаються з потребою заміни цієї деталі, можуть підібрати відповідний варіант на сайті AKS.UA

    Теми цього довгочиту:

    Шлейф Для Мобільного
  • Чохли для iPhone 15: повний гід по кольорах, матеріалах і виробниках

    Перед тим як вибрати чохли для iPhone 15, варто визначитися, що саме ви хочете отримати від аксесуара. Комусь потрібен тонкий прозорий корпус, інший покупець шукає посилений захист, а для когось вирішальним стане колір або підтримка MagSafe

    Теми цього довгочиту:

    Чохли Для Iphone
  • Як побудована програма Meest China Academy

    Курс про товарний бізнес охоплює різні етапи роботи: від пошуку ідеї до перевірки товару, логістики та масштабування. Програма Meest China Academy містить 17 модулів, які послідовно розкривають ці теми без зведення всього навчання до однієї універсальної поради.

    Теми цього довгочиту:

    Meest
  • Які технології використані в Айфон 17

    Айфон 17 поєднує OLED-дисплей із частотою до 120 Гц, чип A19, дві камери Fusion 48 Мп і швидке заряджання через USB-C. У COMFY можна купити Айфон 17 з накопичувачем на 256 або 512 ГБ, вибравши конфігурацію відповідно до обсягу фото, відео та застосунків

    Теми цього довгочиту:

    Iphone 17
Поділись своїми ідеями в новій публікації.
Ми чекаємо саме на твій довгочит!
O P
O P@cr1m3s

4Довгочити
291Перегляди
10Підписники
На Друкарні з 30 квітня 2023

Більше від автора

Це також може зацікавити:

Коментарі (1)

Потужно

Це також може зацікавити: