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

Закінчуємо збирати власний printf

Частина 1

Частина 2

В заключній частині напишемо обробку останніх чотирьох прапорців. Почнемо з найпростішого - %u. В минулій частині ми описали обробку типу int, тепер наше завдання трохи спрощене, тому що нам не потрібно обробляти негативні значення. %u використовується для форматування змінних типу unsigned int, але використовувати конвертер для типу int ми не можемо, оскільки ми можемо отримати неочікувані результати. Спочатку створимо просту програму, котра виведе діапазони значень з котрими нам доведеться працювати:

#include <stdio.h>
// бібліолтека котра містить в собі діапазони для всіх стандартних типів
#include <limits.h>

int main(void){

    printf("Maximum Integer Value: %d\n", INT_MAX);
    printf("Minimum Integer Value: %d\n", INT_MIN);
    printf("Maximum Unsigned Integer Value: %u\n", UINT_MAX);

	return 0;
}

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

Maximum Integer Value: 2147483647
Minimum Integer Value: -2147483648
Maximum Unsigned Integer Value: 4294967295

Unsigned int зберігає лише позитивні значення, тому мінімальне значення для цього типу - це 0. Тому, якщо ми будемо оперувати позитивними значеннями в межах від 0 до 2147483647, то все буде працювати нормально. Однак, коли ми вийдемо за ці межі, відбудеться дещо цікаве. Спробуємо обробити різні значення змінних типу unsigned int за допомогою нашої програми:

...
int main() {
		unsigned int a = 0;
		unsigned int b = 2147483647;
		unsigned int c = 2147483648;
		unsigned int d = 4294967295;


		myprintf("0 == %d\n", a);
		myprintf("2147483647 == %d\n", b);
		myprintf("2147483648 == %d\n", c);
		myprintf("4294967295 == %d\n", d);	


		return 0;
}

Отримаємо:

0 == 0
2147483647 == 2147483647
2147483648 == -2147483648
4294967295 == -1

Зовсім не те на що ми очікували. Для охочих дізнатися, чому так відбувається варто познайомитись з тим в якому виді представляються значення типу int (посилання).

Напишемо функції для обробки прапорця %u використовуючи аналогічні функції, що ми написали раніше для прапорця %d:

int uint_len(unsigned int n){

	int	count = 1;

	while (n > 9) {
		count++;
		n /= 10;
	}

	return count;
}


char *uint_toa(unsigned int n){
	int n_len;
	char *result;

	n_len = uint_len(n);
	result = malloc((n_len + 1) * sizeof(char));
	if (!result)
		return (NULL);
	result[n_len] = '\0';
	while (--n_len >= 0)
	{
		result[n_len] = (n % 10) + '0';
		n /= 10;
	}
	return (result);
}


int printu(unsigned int n){
	char *number;
	int result = 0;
	
	number = uint_toa(n);
	printstring(number);
	result = str_len(number);
	free(number);
	return (result);
}

// не забуваєсо додати новий прапорець 'u'
int	handle_flag(va_list args, char flag)
{
    if (flag == 'u')
	return (printu(va_arg(args, unsigned int)));
    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);
}

Скомпілюємо попередній приклад використовуючи %u:

int main() {
		unsigned int a = 0;
		unsigned int b = 2147483647;
		unsigned int c = 2147483648;
		unsigned int d = 4294967295;


		myprintf("0 == %u\n", a);
		myprintf("2147483647 == %u\n", b);
		myprintf("2147483648 == %u\n", c);
		myprintf("4294967295 == %u\n", d);	

    
    return 0;
}

Отримаємо:

0 == 0
2147483647 == 2147483647
2147483648 == 2147483648
4294967295 == 4294967295

Принаймні для тестових значень змінних отримали вірний результат.

Перейдемо до наступного прапорця - %x (%X). Цей прапорець відрізняється від %u тим, що нам потрібно представити число у шістнадцятковому форматі і в залежності від регістру виводити великі (%X) або малі (%x) літери в результаті. Ми також будемо працювати з цілими позитивними числами, але розширимо діапазон до unsigned long, щоб мати підтримку значень від 0 до 2^64-1 (це нам знадобиться пізніше). Використовуючи функції обробки %u як основу, напишемо аналогічні функції для %x:

int uhex_len(unsigned long n)
{
	int count = 1;
	
	while (n > 15) {
		count++;
		n /= 16;
	}
	return count;
}

// base використовуєтьс для форматування регістру літер
char *uhex_toa(unsigned long n, char base)
{
	int tmp;
	int n_len;
	char *result;

	n_len = uhex_len(n);
	result = (char *)malloc((n_len + 1) * sizeof(char));
	result[n_len] = '\0';
	while (--n_len >= 0)
	{
		tmp = (n % 16);
		if (tmp > 9)
			result[n_len] = base + tmp - 10;
		else
			result[n_len] = tmp + '0';
		n /= 16;
	}
	return result;
}

int printhex(unsigned long n, char base)
{
	char *number;
	int	 result;

	result = 0;
	number = uhex_toa(n, base);
	printstring(number);
	result = str_len(number);
	free(number);

	return result;
}

Додамо дві нові умови до обробника прапорців:

...
  if (flag == 'x')
    return (printhex(va_arg(args, unsigned long), 'a'));
  if (flag == 'X')
    return (printhex(va_arg(args, unsigned long), 'A'));
...

Випробуємо наші функції на попередніх прикладах:

int main() {
  unsigned long a = 0;
  unsigned long b = 2147483647;
  unsigned long c = 2147483648;
  unsigned long d = 4294967295;
  
  		
  myprintf("%x\n", a);
  myprintf("%x\n", b);
  myprintf("%x\n", c);
  myprintf("%x\n", d);
  		
  myprintf("%X\n", a);
  myprintf("%X\n", b);
  myprintf("%X\n", c);
  myprintf("%X\n", d);
    
  return 0;
}

/*
0
7fffffff
80000000
ffffffff
0
7FFFFFFF
80000000
FFFFFFFF
*/

Отже, ми підходимо до фінішу, і залишився лише один прапорець - %p, який використовується для виводу вказівників. Вказівники виводяться у форматі шістнадцяткових чисел з префіксом "0x". Ми вже створили функцію для конвертації десяткових чисел у шістнадцятковий формат, тому залишилося лише створити функцію, яка буде додавати префікс "0x". Вказівник може вказувати на практично будь-яку комірку пам'яті. З огляду на те, що більшість сучасних процесорів мають 64-бітну архітектуру, на попередньому кроці, ми розширили діапазон оброблюваних значень до unsigned long. Отримаємо:

int printpointer(unsigned long n)
{
  char *number;
  int result = 0;
  
  // використовуємо раніше створені функції
  number = uhex_toa(n, 'a');
  // виводимо префікс
  printstring("0x");
  printstring(number);
  result = str_len(number) + 2;
  free(number);
	
  return result;
}

Залишилась лише невелика деталь - обробка випадків, коли вказівник вказує на звільнену комірку пам'яті або вказівник ще не має жодного значення. Додамо обробку для NULL-вказівників і оновимо функцію printPointer:

int printpointer(unsigned long n)
{
  char *number;
  int result;

  result = 0;
  if (!n) {
    // наша функція повинна відтворювати поведінку
    // printf, саму тому для NULL вказівників ми виводмио (nil)
    write(1, "(nil)", 5);
    result = 5;
  }
  else {
    number = uhex_toa(n, 'a');
    printstring("0x");
    printstring(number);
    result = str_len(number) + 2;
    free(number);
  }
  return result;
}

В раніше створених функціях нам залишилось ще одне місце куди слід додати обробку NULL-вказівників, а саме функція printstring:

int printstring(char *str){
    int len  = 0;
    
    if (!str) {
	write(1, "(null)", 6);
	len = 6;
    }
    else {
        len = str_len(str);
        write(1, str, len);
    }
    return len;
}

Додамо нову умову для обробника прапорців:

...
    if (flag == 'p')
	return (printpointer(va_arg(args, unsigned long)));
...

Протестуємо роботу функції:

int main() {
  unsigned long a = 0;
  unsigned long b = 2147483647;
  unsigned long c = 2147483648;
  unsigned long d = 4294967295;


		
  myprintf("%p\n", a);
  myprintf("%p\n", b);
  myprintf("%p\n", c);
  myprintf("%p\n", d);
  // подивимось де в пам'яті знаходяться наші змінні
  myprintf("%p\n", &a);
  myprintf("%p\n", &b);
  myprintf("%p\n", &c);
  myprintf("%p\n", &d);
  return 0;
}
/*
(nil)
0x7fffffff
0x80000000
0xffffffff
0x7ffc3a970228
0x7ffc3a970230
0x7ffc3a970238
0x7ffc3a970240
*/

Таким чином, нам вдалося створити аналог printf, познайомитись ближче з деякими стандартними бібліотеками C і роботою операційної системи. Однак, є багато місць для подальшого покращення. Ось декілька ідей:

  1. Розбиття функцій на окремі файли.

  2. Створення make-файлу.

  3. Перевірка попереджень компілятора.

  4. Перевірка пам'яті за допомогою Valgrind.

  5. Написання тестів.

Код можна знайти за посиланням, дякую за увагу.

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

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

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

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

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

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

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

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

  • Огляд на Linux Mint. Чи комфортно звичайному користувачеві використовувати Linux?

    Linux. З чим асоціюється у вас це слово? Для більшості людей слово «Linux» асоціюється з чимось важким та незрозумілим, де багато коду та ним користуються одні хакери та програмісти. В цій статті ми розберемось чому це не так страшно, як може здатись, на перший погляд

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

    Операційні Системи
  • Damn Small Linux - диструбитив який повертає життя старим комп’ютерам

    DSL 2024 відродився як компактний дистрибутив Linux, спеціально призначений для комп'ютерів з низькими характеристиками x86. Він вміщує багато програм у маленький пакет. Усі програми вибрані за їх функціональність, невеликий розмір та низькі вимоги до залежностей.

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

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

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

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

    Linux

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

Підтримайте автора першим.
Напишіть коментар!

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

  • Огляд на Linux Mint. Чи комфортно звичайному користувачеві використовувати Linux?

    Linux. З чим асоціюється у вас це слово? Для більшості людей слово «Linux» асоціюється з чимось важким та незрозумілим, де багато коду та ним користуються одні хакери та програмісти. В цій статті ми розберемось чому це не так страшно, як може здатись, на перший погляд

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

    Операційні Системи
  • Damn Small Linux - диструбитив який повертає життя старим комп’ютерам

    DSL 2024 відродився як компактний дистрибутив Linux, спеціально призначений для комп'ютерів з низькими характеристиками x86. Він вміщує багато програм у маленький пакет. Усі програми вибрані за їх функціональність, невеликий розмір та низькі вимоги до залежностей.

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

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

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

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

    Linux