結論難しい
機能
brace.h
をインクルードして使用します。
展開された値を配列へ1つずつ格納する機能と、スペース区切りで戻り値にする2種類の機能が使えます。
配列へ代入した場合は戻り値はvoid
です。
配列に代入する際、配列の要素数が足りない場合プログラムを強制終了します。
戻り値は文字列になります。
ただの展開しか出来ないので1つ飛ばしなどは出来ません。
環境はgcc MinGW 6.3.0
です。
考え方
{1..10}
みたいなことは出来ないので関数にしました。
c言語はオーバーロードが出来ないと思っていたのですが、C11から追加された_Generic
を使用すると出来ました。
配列に格納する場合は、配列と展開したい最初の定数と最後の定数を、戻り値にする場合は展開したい最初の定数と最後の定数を引数にします。
かなり無理やり作っているのでもっと良いやり方があるはず(まずc言語でブレース展開やろうと思わないはず)。
ちなみにブレース展開なのに{}
要素が全くないのは突っ込まないでください。
実際のコード
/* 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_function
やdouble_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で書くの楽。