LoginSignup
2
0

More than 5 years have passed since last update.

c言語でブレース展開をやろうとした

Last updated at Posted at 2018-03-05

結論難しい

機能

brace.hをインクルードして使用します。
展開された値を配列へ1つずつ格納する機能と、スペース区切りで戻り値にする2種類の機能が使えます。
配列へ代入した場合は戻り値はvoidです。
配列に代入する際、配列の要素数が足りない場合プログラムを強制終了します。
戻り値は文字列になります。
ただの展開しか出来ないので1つ飛ばしなどは出来ません。
環境はgcc MinGW 6.3.0です。

考え方

{1..10}みたいなことは出来ないので関数にしました。
c言語はオーバーロードが出来ないと思っていたのですが、C11から追加された_Genericを使用すると出来ました。
配列に格納する場合は、配列と展開したい最初の定数と最後の定数を、戻り値にする場合は展開したい最初の定数と最後の定数を引数にします。
かなり無理やり作っているのでもっと良いやり方があるはず(まずc言語でブレース展開やろうと思わないはず)。
ちなみにブレース展開なのに{}要素が全くないのは突っ込まないでください。

brace.PNG

実際のコード

/* main.c */
#include <stdio.h>
#include "brace.h"

int main (void) {
    int number[100];
    char str[100];

    // int型の配列への代入
    brace(number, 10, 1);
    for (int i = 0; i < 10; i++) {
        printf("%d ", number[i]);
    }
    puts("");
    brace(number, 0, 9);
    for (int i = 0; i < 10; i++) {
        printf("%d ", number[i]);
    }
    puts("");

    // char型の配列への代入
    brace(str, 'a', 'z');
    for (int i = 0; i < 26; i++) {
        printf("%c ", str[i]);
    }
    puts("");
    brace(str, 'Z', 'A');
    for (int i = 0; i < 26; i++) {
        printf("%c ", str[i]);
    }
    puts("");

    // int型での戻り値
    printf("%s\n", brace(10, 1));
    printf("%s\n", brace(0, 9));

    // char型での戻り値
    puts(brace((char)'a', 'z'));
    puts(brace((char)'Z', 'A'));

    return 0;
}
/* brace.c */

/** ブレース展開
 * 関数の使い方
 *
 * int型の配列への代入
 * brace(代入先配列, 最初の整数定数, 最後の整数定数);
 *
 * char型の配列への代入
 * brace(代入先配列, 最初の文字定数, 最後の文字定数);
 *
 * int型での戻り値
 * brace(最初の整数定数, 最後の整数定数);
 *
 * char型での戻り値
 * brace((char)最初の文字定数, 最後の文字定数);
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>

void int_brace (int *number, ...);
void char_brace (char *str, ...);
char *return_int (int first, ...);
char *return_char (int first, ...);
int repositioning (int first, int last);
int check_length (int number);

char *brace_value;

#define brace(var, ...) _Generic((var) \
    , int *:        int_brace \
    , char *:       char_brace \
    , const char *: char_brace \
    , int:          return_int \
    , char:         return_char \
    , const char:   return_char \
)(var, __VA_ARGS__, sizeof(var))

void int_brace (int *number, ...) {
    va_list list;
    va_start(list, number);
    int first = va_arg(list, int);
    int last  = va_arg(list, int);
    int len   = va_arg(list, int);
    va_end(list);

    if (len / sizeof(int) < abs(first - last) + 1) {
        puts("I will forcibly quit Program from buffer overflow.");
        exit(0);
    }

    int i;
    if (first < last) {
        for (i = 0; first <= last; i++, first++) {
            number[i] = first;
        }
    } else {
        for (i = 0; first >= last; i++, first--) {
            number[i] = first;
        }
    }

    return;
}

void char_brace (char *str,  ...) {
    va_list list;
    va_start(list, str);
    int first = va_arg(list, int);
    int last  = va_arg(list, int);
    int len   = va_arg(list, int);
    va_end(list);

    if (len < abs(first - last) + 1) {
        puts("I will forcibly quit Program from buffer overflow.");
        exit(0);
    }

    int i;
    if (first < last) {
        for (i = 0; first <= last; i++, first++) {
            str[i] = first;
        }
    } else {
        for (i = 0; first >= last; i++, first--) {
            str[i] = first;
        }
    }

    str[i] = '\0';
    return;
}

char *return_int (int first, ...) {
    va_list list;
    va_start(list, first);
    int last = va_arg(list, int);
    va_end(list);

    free(brace_value);
    brace_value = (char*)malloc(repositioning(first, last));
    sprintf(brace_value, "%d", first);

    if (first < last) {
        for (first++; first <= last; first++) {
            sprintf(brace_value, "%s %d", brace_value, first);
        }
    } else {
        for (first--; first >= last; first--) {
            sprintf(brace_value, "%s %d", brace_value, first);
        }
    }

    return brace_value;
}

char *return_char (int first, ...) {
    va_list list;
    va_start(list, first);
    int last = va_arg(list, int);
    va_end(list);

    free(brace_value);
    brace_value = (char*)malloc((abs(last - first) + 1) * 2);
    sprintf(brace_value, "%c", first);

    if (first < last) {
        for (first++; first <= last; first++) {
            sprintf(brace_value, "%s %c", brace_value, first);
        }
    } else {
        for (first--; first >= last; first--) {
            sprintf(brace_value, "%s %c", brace_value, first);
        }
    }

    sprintf(brace_value, "%s%c", brace_value, '\0');
    return brace_value;
}

int repositioning (int first, int last) {
    if (first > last) {
        const int tmp = first;
        first = last;
        last = tmp;
    }

    int count;
    for (count = 0; first < last; first++) {
        count += check_length(first) + 1;
    }
    return count;
}

int check_length (int number) {
    int digit;
    for (digit = 0; number != 0; digit++) {
        number /= 10;
    }
    return digit;
}

説明

関数の使い方

まずはbraceの使い方から。
代入先配列は十分な長さを用意してください。
文字定数はint型と判断されてしまうので、仕方なくchar型にキャストしています。
もっと良いやり方があれば教えて下さい。

// int型の配列への代入
brace(代入先配列, 最初の整数定数, 最後の整数定数);
// char型の配列への代入
brace(代入先配列, 最初の文字定数, 最後の文字定数);
// int型での戻り値
brace(最初の整数定数, 最後の整数定数);
// char型での戻り値
brace((char)最初の文字定数, 最後の文字定数);

braceの実態

実はbraceは関数ではなく、型に応じて適切な関数を呼ぶマクロです。
配列への代入と戻り値とで引数の数が違うので可変長引数を使用しています。

#define brace(var, ...) _Generic((var) \
    , int *:        int_brace \     // 整数型配列
    , char *:       char_brace \    // 文字列型配列
    , const char *: char_brace \    // 文字列型配列
    , int:          return_int \    // 整数定数
    , char:         return_char \   // 文字定数
    , const char:   return_char \   // 文字定数
)(var, __VA_ARGS__, sizeof(var))

_Genericとは?

_Genericは任意の変数の型に応じて適切な関数を呼ぶ機能です。
int_functiondouble_functionが関数名、(var)が引数です。
char型はconstも書かないと動きません。
_Genericはどの関数も同じ引数でなければ行けません、なので関数にも可変長引数を使用にします。

#include <stdio.h>

#define generic(var) _Generic((var) \
    , int:          int_function \
    , double:       double_function \
    , char *:       string_function \
    , const char *: string_function \
)(var)

void int_function(int var);
void double_function(double var);
void string_function(char *var);

int main (void) {
    generic(1);        // 1:int
    generic(1.0);      // 1.0:double
    generic("string"); // string:string
    return 0;
}

void int_function(int var) {
    printf("%d:int\n", var);
    return;
}

void double_function(double var) {
    printf("%.1lf:double\n", var);
    return;
}

void string_function(char *var) {
    printf("%s:string\n", var);
    return;
}

可変長引数とは?

可変長引数とは任意の数の引数を受け取れる機能です。
可変長引数を使用するにはstdarg.hをインクルードします。
...で使えます!
可変長引数はva_listで可変長引数を保持する変数を作り、va_startで開始し、va_endで終了、かつva_startからva_endの中でしか使用出来ないなど色々ややこしい機能です。
ちなみにva_argで可変長引数の中身を使用し、その後一つづつ次の中身に変わっていきます。
詳しくは「c言語 可変長引数」等で検索してください。
下のコードでは最初に何個の足し算をするかを指定し、その後ろに足し算したい数字を渡します。
そして指定された分の可変長引数を使用し、足し算を行います。
あと__VA_ARGS__はマクロで受け取ったパラメータを使用するのに使います。

#include <stdio.h>
#include <stdarg.h>

int sum (int num, ...);

int main (void) {
    printf("%d\n", sum(5, 1, 2, 3, 4, 5)); // 15
    printf("%d\n", sum(3, 1, 2, 3, 4, 5)); // 6
    return 0;
}

int sum (int num, ...) {
    va_list list;
    int sum = 0;
    va_start(list, num);
    for (int i = 0; i < num; i++) {
        sum += va_arg(list, int);
    }
    va_end(list);

    return sum;
}

配列の長さを図る

sizeofを使用しているのはバッファオーバーフロー対策です。
配列に代入する際に、代入先配列より代入元のが大きい場合、配列に入り切らずにオーバーフローしてしまいます。
関数内で配列を図ろうとしても、関数には配列の先頭番地のポインタしかないので、配列の長さを図れません。
なのでマクロ内で配列の長さを図って関数に渡しています。
戻り値の場合、配列を使用しないので可変長引数で受け取って使っていません。

#include <stdio.h>

void hoge(int *number);
void neko(int len);

#define fuga(var) neko(sizeof(var) / sizeof(int))

int main (void) {
    int number[100];
    int len = sizeof(number) / sizeof(int);
    printf("%d\n", len); // 100
    hoge(number);
    fuga(number);
    return 0;
}

void hoge(int *number) {
    int len = sizeof(number) / sizeof(int);
    printf("%d\n", len); // 1
    return;
}

void neko(int len) {
    printf("%d\n", len); // 100
    return;
}

配列へ代入

まず受け取った可変長引数を変数に代入します。
その後配列の長さが足りているかチェックして足りていない場合はプログラムを強制終了します。
整数定数(文字定数)が増えているか、減っているかを判別し、適切に配列へ代入します。
文字定数の場合は最後に'\0'を代入します。

void int_brace (int *number, ...) {
    va_list list;
    va_start(list, number);
    int first = va_arg(list, int);
    int last  = va_arg(list, int);
    int len   = va_arg(list, int);
    va_end(list);

    if (len / sizeof(int) < abs(first - last) + 1) {
        puts("I will forcibly quit Program from buffer overflow.");
        exit(0);
    }

    int i;
    if (first < last) {
        for (i = 0; first <= last; i++, first++) {
            number[i] = first;
        }
    } else {
        for (i = 0; first >= last; i++, first--) {
            number[i] = first;
        }
    }

    return;
}

void char_brace (char *str,  ...) {
    va_list list;
    va_start(list, str);
    int first = va_arg(list, int);
    int last  = va_arg(list, int);
    int len   = va_arg(list, int);
    va_end(list);j

    if (len < abs(first - last) + 1) {
        puts("I will forcibly quit Program from buffer overflow.");
        exit(0);
    }

    int i;
    if (first < last) {
        for (i = 0; first <= last; i++, first++) {
            str[i] = first;
        }
    } else {
        for (i = 0; first >= last; i++, first--) {
            str[i] = first;
        }
    }

    str[i] = '\0';
    return;
}

戻り値に(int)

まずはint型から、代入と同じく受け取った可変長引数を変数に代入します。
mallocを使用している変数を、returnの戻り値として使用しているので、次に関数を呼んだ時にfreeしています。(freeする場所が見当たらない…)
次にグローバル変数brace_valueを展開された文字列が入る大きさまで広げます。
整数は文字と違って10なら二桁1000なら四桁と桁数が違うので、間のスペースも含めて配列の要素数を決めるのが面倒です。
関数repositioningで桁を図ります。
次にsprintfを使って文字列を作ります。
sprintfはバッファオーバーフローの危険性があるので、通常snprintfを使うのですが、今回はすでにmallocで要素を広げてるのでsprintfを使用しています。

char *return_int (int first, ...) {
    va_list list;
    va_start(list, first);
    int last = va_arg(list, int);
    va_end(list);

    free(brace_value);
    brace_value = (char*)malloc(repositioning(first, last));
    sprintf(brace_value, "%d", first);

    if (first < last) {
        for (first++; first <= last; first++) {
            sprintf(brace_value, "%s %d", brace_value, first);
        }
    } else {
        for (first--; first >= last; first--) {
            sprintf(brace_value, "%s %d", brace_value, first);
        }
    }

    return brace_value;
}

何桁かしりたい

repositioningが要素数を決める関数です。
まずlastが大きくなるように入れ替えます。
その後check_lengthで桁数を図り、スペース分を足した数を戻り値にします。

int repositioning (int first, int last) {
    if (first > last) {
        const int tmp = first;
        first = last;
        last = tmp;
    }

    int count;
    for (count = 0; first < last; first++) {
        count += check_length(first) + 1;
    }
    return count;
}

int check_length (int number) {
    int digit;
    for (digit = 0; number != 0; digit++) {
        number /= 10;
    }
    return digit;
}

戻り値に(char)

基本的にはint型と同じです。
mallocをする際にchar型は1バイト固定なので、repositioningを使用しません。
文字定数なので最後に'\0'を代入します。

char *return_char (int first, ...) {
    va_list list;
    va_start(list, first);
    int last = va_arg(list, int);
    va_end(list);

    free(brace_value);
    brace_value = (char*)malloc((abs(last - first) + 1) * 2);
    sprintf(brace_value, "%c", first);

    if (first < last) {
        for (first++; first <= last; first++) {
            sprintf(brace_value, "%s %c", brace_value, first);
        }
    } else {
        for (first--; first >= last; first--) {
            sprintf(brace_value, "%s %c", brace_value, first);
        }
    }

    sprintf(brace_value, "%s%c", brace_value, '\0');
    return brace_value;
}

まとめ

いろいろ勉強になりました。
Qiitaをatomで書くの楽。

2
0
4

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
2
0