Друкарня від 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. код можна знайти за посиланням.

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

  • Вітаємо з Різдвом Христовим!

    Друкарня та платформа WE.UA вітають всіх наших читачів та авторів зі світлим святом Різдва! Зичимо всім українцям довгожданого миру, міцного здоровʼя, злагоди, родинного затишку та втілення всього доброго і прекрасного, чого вам побажали колядники!

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

    Різдво
  • Каблучки – прикраси, які варто купувати

    Ювелірні вироби – це не тільки спосіб витратити гроші, але і зробити вигідні інвестиції. Бо вартість ювелірних виробів з кожним роком тільки зростає. Тому купуючи стильні прикраси, ви вигідно вкладаєте кошти.

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

    Як Вибрати Каблучку
  • П'ять помилок у виборі домашнього текстилю, які псують комфорт сну

    Навіть ідеальний матрац не компенсує дискомфорт, якщо текстиль підібрано неправильно. Постільна білизна безпосередньо впливає на терморегуляцію, стан шкіри та глибину сну. Більшість проблем виникає не через низьку якість виробів, а через вибір матеріалів та подальшу експлуатацію

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

    Домашній Текстиль
  • Як знайти житло в Києві

    Переїжджаєте до Києва і шукаєте житло? Дізнайтеся, як орендувати чи купити квартиру, перевірити власника та знайти варіанти, про які зазвичай не говорять.

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

    Агентство Нерухомості
  • Як заохотити дитину до читання?

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

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

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

261Прочитань
4Автори
10Читачі
На Друкарні з 30 квітня

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

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

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

Потужно

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