Друкарня від 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).

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

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

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

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

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

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

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

    Шлейф - це тонка пластикова стрічка з ледь видимими доріжками і саме вона зʼєднує важливі компоненти мобільного, без яких він не запрацює. Власники пристроїв 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

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

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

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

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

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

🔸https://twitter.com/drukarniaua

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

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