7
3

More than 1 year has passed since last update.

【C言語】軽くprintf関数を自作する

Last updated at Posted at 2021-12-08

:white_check_mark:概要

軽くprintf関数を自作します、フラグとかはつけません。
Qiitaの絵文字機能をたくさん使いたいなーと思って書いてみました:japanese_goblin:

:white_check_mark:実装内容

大体の決まりごとはこんな感じでやっていくよ:airplane:

・完成ファイル
    libftprintf.a
・使う関数
    malloc, free, write, va_start, va_arg, va_copy, va_end

・対応するフォーマット指定子
    c, s, p, d, i, u, x, X, %

:white_check_mark:使用するサブ関数の実装

上の使う関数以外は使わないので標準関数から実装していくよ
libftってフォルダーに格納する。

ヘッダーファイルとMakefileはちょっと省略:yum:

libft.h
#ifndef LIBFT_H
# define LIBFT_H

# include <stdlib.h>
# include <unistd.h>

<以下関数のプロトタイプ宣言>

#endif
NAME = libft.a
CC = gcc
INC  = -I.

SRCS = <以下の関数>

OBJS = $(SRCS:%.c=%.o)

all:    $(NAME)

.c.o:
        $(CC) $(INC) -c $< -o $@

$(NAME):$(OBJS)
        ar rcs $(NAME) $(OBJS)

clean:
        rm -f $(OBJS)

fclean: clean
        rm -f $(NAME)

re: fclean all

みんな大好き文字数カウントする関数
\0が出るまでカウンターを+1していく

ft_strlen.c
#include "libft.h"

size_t  ft_strlen(const char *s)
{
    size_t  return_len;

    return_len = 0;
    while (s[return_len] != '\0')
        return_len ++;
    return (return_len);
}

与えられたメモリ領域をint cで初期化する。
順番に代入していく

ft_memset.c
#include "libft.h"

void    *ft_memset(void *dst, int c, size_t n)
{
    size_t          count;
    unsigned char   *str;

    count = 0;
    str = (unsigned char *)dst;
    while (count < n)
    {
        str[count] = (unsigned char)c;
        count++;
    }
    return (dst);
}

ft_memset()\0を引数として渡せば完成
本家GNUのbzero()もこうやって実装されています

ft_bzero.c
#include "libft.h"

void    ft_bzero(void *s, size_t n)
{
    ft_memset(s, '\0', n);
}

malloc()を使用して確保した領域をft_bzero()で初期化する

ft_calloc.c
#include "libft.h"

void    *ft_calloc(size_t count, size_t size)
{
    void    *result;

    if (count == 0 || size == 0)
    {
        count = 1;
        size = 1;
    }
    result = malloc(count * size);
    if (!result)
        return (result);
    ft_bzero(result, count * size);
    return (result);
}

ft_calloc()で確保した領域に引数を1文字ずつ代入していく。
リターンするのは確保したアドレス。

ft_strdup.c
#include "libft.h"

char    *ft_strdup(const char *str)
{
    char    *result;
    size_t  i;

    result = (char *)ft_calloc(ft_strlen(str) + 1, sizeof(char));
    if (!result)
        return (0);
    i = 0;
    while (str[i])
    {
        result[i] = str[i];
        i++;
    }
    return (result);
}

簡易的にitoa()を実装している。
ft_calloc()で桁数分確保した領域に+48してINTをASCIIに変換した物を代入してく。

ft_itoa.c
#include "libft.h"

static size_t   ft_count(int num)
{
    int     i;
    size_t  size;

    i = num;
    size = 1;
    if (i < 0)
    {
        i = -i;
        size++;
    }
    while (10 <= i)
    {
        i = i / 10;
        size++;
    }
    return (size);
}

static char *ft_return_intmin(void)
{
    char    *res;

    res = (char *)ft_calloc(12, sizeof(char));
    if (!res)
        return (NULL);
    ft_memcpy(res, "-2147483648", 11);
    return (res);
}

char    *ft_itoa(int n)
{
    size_t  size;
    char    *result;

    if (n == -2147483648)
        return (ft_return_intmin());
    size = ft_count(n) - 1;
    result = (char *)ft_calloc(size + 2, sizeof(char));
    if (!result)
        return (NULL);
    if (n < 0)
    {
        n = -n;
        result[0] = '-';
    }
    while (10 <= n)
    {
        result[size--] = 48 + (n % 10);
        n = n / 10;
    }
    result[size] = 48 + n;
    return (result);
}

引数が'A' <= c && c <= 'Z'を満たしたときに引数+32した値を返す
+32は大文字の場合小文字に変換される
ASCIIコード表を参照してね:thumbsup_tone1:
それ以外はそのまま返す。

ft_tolower.c
#include "libft.h"

int ft_tolower(int c)
{
    if ('A' <= c && c <= 'Z')
        return (c + 32);
    return (c);
}

1文字だけ出力する関数
ファイルディスクリプタは1で標準出力になる

ft_putchar_fd.c
#include "libft.h"

void    ft_putchar_fd(char c, int fd)
{
    write(fd, &c, 1);
}

文字列を出力する関数
文字数をカウントして、その分write()にわたす

ft_putstr_fd.c
#include "libft.h"

void    ft_putstr_fd(char *s, int fd)
{
    if (!s)
        return ;
    write(fd, s, ft_strlen(s));
}

メモリ領域に任意の値をコピーするmemcpy追記しました!

ft_memcpy.c
#include "libft.h"

void    *ft_memcpy(void *dst, const void *src, size_t n)
{
    size_t          cnt;
    char            *too;
    const char      *frm;

    frm = (const char *)src;
    too = (char *)dst;
    cnt = 0;
    if (frm == NULL && too == NULL)
        return (NULL);
    while (cnt < n)
    {
        too[cnt] = frm[cnt];
        cnt++;
    }
    return (dst);
}

:white_check_mark:フロー

大まかにどんな感じで処理するかを決める:muscle::muscle::muscle:

ファイル 役目
ft_printf.c 可変長引数を受け取る。引数をft_strdupで保存してから、それ以降の引数をft_count_output()に渡す(出力)。戻り値を返す
ft_printf.h 使用する関数のリストとインクルードするファイルを書く
ft_count_output.c フォーマット識別子以外を出力。%が来たら引数をft_treat_something()に識別子と一緒に渡す
ft_treat_something.c それぞれの識別子を扱う以下のft_treat_*()に引数を渡す。
ft_treat_char.c フォーマット識別子%cの際に、引数の1文字を出力、文字数をカウントして文字数を返す。(戻り値は必ず1となる)
ft_treat_string.c フォーマット識別子%cの際に、引数の文字列を出力、文字数をカウントして文字数を返す。
ft_treat_point.c フォーマット識別子%pの際に、引数のアドレスを出力、アドレスの文字数をカウントして文字数を返す。
ft_treat_int.c フォーマット識別子%dか%iの際に、引数の値を数で出力、文字数をカウントして文字数を返す。
ft_treat_uint.c フォーマット識別子%uの際に、引数を正の整数で出力、文字数をカウントして文字数を返す。
ft_treat_hexa.c フォーマット識別子%xか%Xの際に、整数の引数を16進数で出力、文字数をカウントして文字数を返す。xは小文字でXは大文字
ft_putstr_count.c 出力するための関数。出力した文字数をカウントして返す。
ft_change_base.c 進数変換をするために使う

:white_check_mark:ft_print.c

引数をft_strdup()に渡して、saveに保存する。
va_start(args, input)va_listargsinputを入れるってことが出来る。
saveargsft_count_output()に渡して、戻り値は出力した文字数。
free()saveを開放し忘れないようにっ!

if (!save) return (0);を追記しました。

ft_print.c
#include "ft_printf.h"

int ft_printf(const char    *input, ...)
{
    const char  *save;
    va_list     args;
    int         num;

    num = 0;
    save = ft_strdup(input);
    if (!save)
        return (0);
    va_start(args, input);
    num = ft_count_output(save, args);
    va_end(args);
    free((char *)save);
    return (num);
}

:white_check_mark:ft_count_output.c

もし、%が来たらft_treat_something()argsとフォーマット識別子を渡す
それ以外なら、出力する
ft_treat_something()からは文字数が返ってくるようにしてるからそれをどんどん足していく
返り値は出力した文字数にする。

ft_count_output.c
#include "ft_printf.h"

int ft_count_output(const char *save, va_list args)
{
    size_t  i;
    size_t  c;

    i = 0;
    c = 0;
    while (save[i])
    {
        if (save[i] == '%')
        {
            i++;
            c += ft_treat_something(save[i], args);
        }
        else
        {
            ft_putchar_fd(save[i], 1);
            c++;
        }
        i++;
        if (!save[i])
            return (c);
    }
    return (c);
}

:white_check_mark:ft_treat_something.c

cの条件によって扱う型が違うから分岐させる
va_arg(args, <変換したい方>)で型変換できるから楽ぅぅ!

ft_treat_something.c
#include "ft_printf.h"

int ft_treat_something(int c, va_list args)
{
    int n;

    n = 0;
    if (c == 'c')
        n = ft_treat_char(va_arg(args, int));
    else if (c == 's')
        n = ft_treat_string(va_arg(args, char *));
    else if (c == 'p')
        n = ft_treat_point((unsigned long long)va_arg(args, void *));
    else if (c == 'd' || c == 'i')
        n = ft_treat_int(va_arg(args, int));
    else if (c == 'u')
        n = ft_treat_uint((unsigned long long)va_arg(args, unsigned long long));
    else if (c == 'x')
        n = ft_treat_hexa(va_arg(args, unsigned int), 1);
    else if (c == 'X')
        n = ft_treat_hexa(va_arg(args, unsigned int), 2);
    else if (c == '%')
        n = ft_putstr_count("%");
    return (n);
}

:white_check_mark:ft_change_base.c

進数変換をする。
第一引数に数値もらって、第二引数に変換する進数を受け取る
どんどん割って行って桁数を把握した後、ft_calloc()で確保した領域に割った結果を入れる

ft_change_base.c
include "ft_printf.h"

static char *change_base(unsigned long long us, int b, char *r, int c)
{
    while (us != 0)
    {
        if ((us % b) < 10)
            r[c - 1] = (us % b) + 48;
        else
            r[c - 1] = (us % b) + 55;
        us /= b;
        c--;
    }
    return (r);
}

char    *ft_point_base(unsigned long long point, int base)
{
    char                *rtn;
    unsigned long long  us;
    int                 count;

    count = 0;
    us = point;
    if (point == 0)
        return (ft_strdup("0"));
    while (point != 0)
    {
        point = point / base;
        count++;
    }
    rtn = ft_calloc(count + 1, sizeof(char));
    if (!rtn)
        return (0);
    rtn = change_base(us, base, rtn, count);
    return (rtn);
}

:white_check_mark:ft_tolower_all.c

与えられた文字を全て小文字に変換する
順番に1文字ずつft_tolower()に突っ込んでるだけですー

ft_tolower_all.c
#include "ft_printf.h"

char    *ft_tolower_all(char *c)
{
    int i;

    i = 0;
    while (c[i])
    {
        c[i] = ft_tolower(c[i]);
        i++;
    }
    return (c);
}

:white_check_mark:ft_treat*.c

char型を受け取って出力。
必ず1文字なのでreturn (1);ってしてる。

ft_treat_char.c
#include "ft_printf.h"

int ft_treat_char(char c)
{
    ft_putchar_fd(c, 1);
    return (1);
}

string型の変数を受け取って出力する。
例外にstrがNULLの時、(null)ってしなきゃいけない。
戻り値は文字数なのでft_strlen()をリターンする。

ft_treat_string.c
#include "ft_printf.h"

int ft_treat_string(char *str)
{
    int n;

    if (str == NULL)
        str = "(null)";
    n = ft_strlen(str);
    ft_putstr_fd(str, 1);
    return (n);
}

渡された引数のアドレスを16進法に変えて全て小文字で出力する。
受け取る時点でアドレスになっているからここでは16進数に変更して小文字にすればいいってこと。
0xを出力してから出力する。

ft_treat_point.c
#include "ft_printf.h"

int ft_treat_point(unsigned long long point)
{
    char    *p;
    int     n;

    p = ft_tolower_all(ft_point_base(point, 16));
    n = ft_putstr_count("0x");
    n += ft_putstr_count(p);
    free(p);
    return (n);
}

整数値を出力する。
ft_itoa()で渡された値を文字に変換して、出力。
カウントしてリターン。

ft_treat_int.c
#include "ft_printf.h"

int ft_treat_int(int i)
{
    char    *str;
    int     n;

    str = ft_itoa(i);
    n = ft_putstr_count(str);
    free(str);
    return (n);
}

正の整数を出力する。
要修正)INTの範囲を超すと、あまりうまくいってない.....:cold_sweat:

修正済み)ft_itoa()を使用せずにそのまま出力する関数作りました。
他のところでこの関数を使用しない理由は、こっちはint範囲超えても回らずに、他は回したいからです!

ft_treat_uint.c
#include "ft_printf.h"

static int  ft_put_unbr_count(unsigned long long n)
{
    char    str;
    unsigned long long tmp;
    size_t  t;

    tmp = n;
    t = 0;
    while (9 < tmp)
    {
        tmp = tmp / 10;
        t++;
    }
    if (9 < n)
        ft_put_unbr_count(n / 10);
    str = '0' + n % 10;
    write(1, &str, 1);
    return (t+1);
}

int ft_treat_uint(unsigned long long uint)
{
    int     n;

    n = 0;
    n = ft_put_unbr_count(uint, 1);
    return (n);
}

第二引数のfが1だった場合にはft_tolower_all()で全て小文字にする
つまり、%xの場合に全て小文字にして%Xの場合は大文字でおっけってこと:ok_hand:
free(hex)で開放も忘れないように!メモリリークします:scream:

ft_treat_hexa.c
#include "ft_printf.h"

int ft_treat_hexa(unsigned int i, int f)
{
    char    *hex;
    int     n;

    if (!i)
        i = 0;
    hex = ft_point_base((unsigned long long)i, 16);
    if (f == 1)
        hex = ft_tolower_all(hex);
    n = ft_putstr_count(hex);
    free(hex);
    return (n);
}

:white_check_mark:ft_putstr_count.c

文字数をカウントして出力
その分を返す関数になってます!

ft_putstr_count.c
#include "ft_printf.h"

int ft_putstr_count(char *c)
{
    int n;

    if (!c)
        return (0);
    n = ft_strlen(c);
    ft_putstr_fd(c, 1);
    return (n);
}

:white_check_mark:Makefile

NAME = libftprintf.a
CC = gcc
FLAGS = -c
LIBFT = ./libft/libft.a

SRCS = ft_change_base.c \
    ft_count_output.c \
    ft_printf.c \
    ft_tolower_all.c \
    ft_treat_hexa.c \
    ft_treat_int.c \
    ft_treat_point.c \
    ft_treat_something.c \
    ft_treat_string.c \
    ft_treat_uint.c \
    ft_treat_char.c \
    ft_putstr_count.c

SURPL_O = ft_change_base.o \
    ft_count_output.o \
    ft_printf.o \
    ft_tolower_all.o \
    ft_treat_hexa.o \
    ft_treat_int.o \
    ft_treat_point.o \
    ft_treat_something.o \
    ft_treat_string.o \
    ft_treat_uint.o \
    ft_treat_char.o \
    ft_putstr_count.o

OBJS = $(SRCS:.c=.o)

$(NAME): $(OBJS)
    $(MAKE) -C ./libft
    cp libft/libft.a $(NAME)
    $(CC) $(FLAGS) $(SRCS)
    ar -rcs $(NAME) $(OBJS)

all : $(NAME)

clean :
    $(MAKE) clean -C ./libft
    rm -rf $(SURPL_O) 
    rm -rf $(OBJS)

fclean : clean
    $(MAKE) fclean -C ./libft
    rm -rf $(NAME)

re : fclean all

:white_check_mark:動作チェック

今回作成した関数は、静的ライブラリにするからちゃんと実装できているか確認する必要あり
メイン関数も作っていく:muscle:

main.c
#include "ft_printf.h"
#include <stdio.h>

int main(void)
{
    int num1 = ft_printf("m_c char  > %c            %c  <   ", 'a', 'b');
    printf("%d \n", num1);
    int num2 = printf("o_c char > %c            %c  <   ", 'a', 'b');
    printf("%d \n", num2);

    printf("\n");
    static char str = '\0';
    num1 = ft_printf("m_c char  > %c            %c  <   ", str, 'b');
    printf("%d \n", num1);
    num2 = printf("o_c char > %c            %c  <   ", str, 'b');
    printf("%d \n", num2);

    printf("\n");
    num1 = ft_printf("m_s string    > %s            %s  <   ", "hey", "siri");
    printf("%d \n", num1);
    num2 = printf("o_s string   > %s            %s  <   ", "hey", "siri");
    printf("%d \n", num2);

    printf("\n");
    num1 = ft_printf("m_s string    > %s            %s  <   ", "", "\0");
    printf("%d \n", num1);
    num2 = printf("o_s string   > %s            %s  <   ", "", "\0");
    printf("%d \n", num2);

    printf("\n");
    int num = 1;
    int *p_num1 = &num;
    num1 = ft_printf("m_p poiner    > %p %p <   ", "hey", p_num1);
    printf("%d \n", num1);
    num2 = printf("o_p poiner   > %p %p <   ", "hey", p_num1);
    printf("%d \n", num2);

    printf("\n");
    num1 = ft_printf("m_d digit > %d            %d  <   ", -123, 0);
    printf("%d \n", num1);
    num2 = printf("o_d digit    > %d            %d  <   ", -123, 0);
    printf("%d \n", num2);

    printf("\n");
    num1 = ft_printf("m_i int   > %i            %i  <   ", -123, 42);
    printf("%d \n", num1);
    num2 = printf("o_i int  > %i            %i  <   ", -123, 42);
    printf("%d \n", num2);

    printf("\n");
    num1 = ft_printf("m_u uint  > %u            %u  <   ", 1, 4321);
    printf("%d \n", num1);
    num2 = printf("o_u uint > %u            %u  <   ", 1, 4321);
    printf("%d \n", num2);

    printf("\n");
    num1 = ft_printf("m_x hex   > %x        %x  <   ", -1, 4321);
    printf("%d \n", num1);
    num2 = printf("o_x hex  > %x        %x  <   ", -1, 4321);
    printf("%d \n", num2);

    printf("\n");
    num1 = ft_printf("m_x hex   > %X        %X  <   ", -1, 42);
    printf("%d \n", num1);
    num2 = printf("o_x hex  > %X        %X  <   ", -1, 42);
    printf("%d \n", num2);

    printf("\n");
    num1 = ft_printf("m_%% %%       > %c            %s  <   ", 'a', "abc");
    printf("%d \n", num1);
    num2 = printf("o_%% %%      > %c            %s  <   ", 'a', "abc");
    printf("%d \n", num2);

    return (0);
}

そしたらコマンド実行

$ make
$ gcc main.c libftprintf.a
$ ./a.out

うおおおおお!
できましたーー:v::v::v::v::v::v:

image.png

:white_check_mark:振り返り

C言語のprintf()を自作しました
30時間くらいかかったかもしれません。大変でした。
ちょっと修正点があります(カモシレマセン)
温かい目で見てくださってありがとうございます:bow::bow::bow:

:white_check_mark:おまけ

:eye: :eye::ear:
 :tongue: < see you!

7
3
11

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3