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

Продовжуємо збирати велосипед або писати власний printf

У попередній частині ми створили основу для власної імплементації функції printf, використовуючи функцію write з бібліотеки unistd.h. Зараз ми спробуємо реалізувати обробку прапорців %i та %d, які використовуються для виводу значень типу int. Ці прапорці мають однакову поведінку в функції printf, а відмінність між ними проявляється лише при використанні їх у функції scanf (детальніше).

Почнемо з простого і використаємо функцію write для виводу значення змінної типу int:

#include <unistd.h>

int main(void){

	int a  = 123;

	write(1, &a, sizeof(a));

	return 0;
}

Скомпілюємо і отримаємо:

[cr1m3s@pc:~]$ gcc foo.c
[cr1m3s@pc:~]$ ./a.out
{

Ми отримали трохи дивний результат "{". Це не є помилкою функції і не просто випадковим значенням з пам'яті. Давайте відкриємо таблицю ASCII і перевіримо:

│ Oct   Dec   Hex   Char
...
│ 173   123   7B    {

Отже, просто так вивести значення типу int у нас не вийде. Для обробки цілочисельних значень нам потрібно написати власний конвертор типу int до типу char або char*.

Давайте почнемо з простого перетворення числа на літеру. Знову звернемось до таблиці ASCII:

       Oct   Dec   Hex   Char
       060   48    30    0                   
       061   49    31    1                       
       062   50    32    2                       
       063   51    33    3                      
       064   52    34    4                     
       065   53    35    5                       
       066   54    36    6                     
       067   55    37    7                      
       070   56    38    8                       
       071   57    39    9   

Тобто щоб вивести числа нам потрібні значення int починаючи від 48 до 57.

#include <unistd.h>

int main(void){

	int a  = 48;
	while (a < 58) {
		write(1, &a, sizeof(a));
		a++;
	}

	return 0;
}

Після компіляції і виконання отримаємо:

[cr1m3s@pc:~]$ gcc foo.c
[cr1m3s@pc:~]$ ./a.out
0123456789

Отже, щоб отримати представлення числа типу int у форматі char, достатньо до значення int додати 48. Напишемо просту функцію для цього:

int write_int(int a){

	if (a < 0 || a > 9)
		return -1;

	// тип char в С зберігає значення в діапазоні від -128 до 127
        // тому ми можемо проводити математичні операції між int та char
        a += '0';        
        write(1, &a, sizeof(a));

	return 0;
}

Це непоганий початок, але таким чином ми можемо виводити лише числа в діапазоні від 0 до 9. Для перетворення більших (або менших:)) чисел нам потрібна значно складніша функція.

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

int int_len(int a){

	int result = 0;
	int n = a;

	if (n == 0)
		return 1;

	if (n < 0)
		result++;

	while (n != 0){
		result++;
		n /= 10;
	}

	return result;
}

Знаючи кількість символів в цілочисленній змінній, ми можемо створити стрічку використовуючи malloc (не забуважємо підключити бібліотеку stdlib.h).

char* int_toa(int a){

	int num;
	int sign;
	int i_len;
	char* result;

	i_len = int_len(a);
        // не забуваємо про '\0' на кінці стрічки
	result = malloc((i_len + 1) * sizeof(char));
        // sign виступає як костиль
        // можна було б просто помножити негативне значення на -1
        // але ми можемо прийти до переповнення
        // INT_MAX --> 2147483647
        // INT_MIN --> -2147483648
        // INT_MIN * -1 > INT_MAX
	sign = 1;
	num = a;

	if (!result)
		return NULL;

	if (num < 0)
		sign = -1;
        // починаємо з кінця
	result[i_len] = '\0';

	while (--i_len >= 0){
                // використовуємо sign щоб отримувати коректне значення
                // тим самим уникаємо використання abs 
		result[i_len] = (num % 10) * sign + '0';
		num /= 10;
	}

	if (sign == -1)
		result[0] = '-';

	return result;
}

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

int printdecimal(int n)
{
	char *number;
	int result;

	number = int_toa(n);
        // використаємо функцію printstring написану в минулій частині
	result = printstring(number);
        // не забуваємо звільнити пам'ять після використання
	free(number);

	return (result);
}

Додамо нові прапорці до функції handle_flag:

int handle_flag(va_list args, char flag)
{
        if (flag == 'd' || flag == 'i')
                return (printdecimal(va_arg(args, int)));
  	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 main() {
    char * str = "%cello %s!%%\n";
    myprintf(str, 'H', "world");
 
    int a = myprintf("%d", -2147483648);
    myprintf("\n");
    int b = myprintf("%d", 0);
    myprintf("\n");
    int c = myprintf("%d", 2147483647);
    myprintf("\n");
 
    myprintf("%i\n", a);
    myprintf("%i\n", b);
    myprintf("%i\n", c);
    
    return 0;
}

Отримаємо:

Hello world!%
-2147483648
0
2147483647
11
1
10

[Execution complete with exit code 0]

Код успішно компілюється, виводить потрібні значення і навіть повертає 0, незважаючи на спроби маніпулювати пам'яттю вручну з використанням malloc і free. Тепер наша функція може обробляти 5 з 9 прапорців. В останній частині розглянемо обробку 16-річних чисел (%x, %X), позитивних цілих чисел (%u) і найцікавіше - вказівників (%p).

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

Дякую за те, що дочитали до кінця.

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

  • CRM keyCRM: зручне рішення для продажів, комунікацій і керування командою

    Успіх компанії залежить від того, наскільки швидко вона здатна опрацьовувати вхідні запити. Коли дані про клієнтів розпорошені між різними месенджерами, виникає хаос. CRM keyCRM пропонує вихід із цієї ситуації, об’єднуючи всі робочі процеси в єдиному зручному інтерфейсі.

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

    Crm
  • Різниця між UX і UI, яку варто зрозуміти ще до першого заняття

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

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

    Ui-ux
  • Логіка змін: як SEO оптимізація прибирає бар’єри до зростання

    Багато компаній приходять у SEO з очікуванням швидкого ривка, але дійсний ефект починається там, де сайт перестають латати точково. Тому в центрі роботи стоїть не окрема дія, а послідовні зміни. Оптимізація сайту має прибирати системні перешкоди, а не маскувати їх новими текстами

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

    Seo
  • Музичний футуризм: неймовірні інструменти XXI століття

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

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

    Музичні Інструменти
  • Стіл – всьому голова? Так, якщо його правильно підібрати

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

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

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

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

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

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

  • 30 років проекту Debian

    16 серпня 1993 року нині покійний Ян Мердок (Ian Murdock) анонсував новий проект під назвою "The Debian Linux Release".

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

    Debian
  • Дайджест новин за жовтень

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

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

    Linux
  • Що таке пакети в Linux, і чому в Windows не так?

    Коли людина тільки приходить в Linux, вона чує багато незрозумілих їй слів. Одним із таких слів є “Пакети“. І ні, це не ті пакети, в котрих рашиків додому відправляють… кхм. Тому давайте розберемося з пакетами в Linux, розглянемо гарні та не дуже сторони.

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

    Linux

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

Вітаю! Вашу роботу опубліковано в Twitter та на Facebook Друкарні.

🔸https://www.facebook.com/drukarniaua

🔸https://twitter.com/drukarniaua

Ваш довгочит дуже цікавий! 🫶

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

  • 30 років проекту Debian

    16 серпня 1993 року нині покійний Ян Мердок (Ian Murdock) анонсував новий проект під назвою "The Debian Linux Release".

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

    Debian
  • Дайджест новин за жовтень

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

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

    Linux
  • Що таке пакети в Linux, і чому в Windows не так?

    Коли людина тільки приходить в Linux, вона чує багато незрозумілих їй слів. Одним із таких слів є “Пакети“. І ні, це не ті пакети, в котрих рашиків додому відправляють… кхм. Тому давайте розберемося з пакетами в Linux, розглянемо гарні та не дуже сторони.

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

    Linux