新しい C 言語の仕様 C11 を試してみた

 [Home]

投稿日 2015/02/28 更新 2016/07/29

はじめに

C 言語ですが、もともとは Unix のシステム記述言語として AT & T のベル研究所で生まれたそうです(1970年代前半)。最初のものは Kernighan & Ritchie (通称 K & R) とか呼ばれていて、現在のものとは文法的にちょっと違ってました。

現在、多く使われているのが ANSI C と呼ばれる仕様で、1989年に仕様が決められたので、C89 とかとも呼ばれるそうです。その後、1999年版の C99 が出て現在は 2011年版の C11 が利用可能になりつつあるようです。しかし、仕様が決められたからと言って、実際の処理系に実装されるまでには時間がかかるし、すべての仕様が盛り込まれるわけではないようです。

 

gcc で試してみた

試した環境は AWS で動作している Ubuntu 14.04LTS です。gcc のバージョンは、

gcc version 4.8.2 (Ubuntu 4.8.2-19ubuntu1)

となってました。


C11 を有効にするには

デフォルトでは ANSI C (C89) になるはずなので、何かオプションがあるかと見てみたらありました。-v (verbose) 付きのヘルプを表示すると出てきます。

$ gcc -v --help
  ....
-std=<standard>          Assume that the input sources are for 
  ....

この standard を c11 にすればよさそうです。


gets_s

ANSI C で使えた gets はセキュリティの問題から C11 では廃止になったそうです。代わりに gets_s 関数というのがあるらしいので使ってみました。

#include <stdio.h>

// char* gets_s(char*, unsigned int);

int main(int argc, char *argb[]) {
  putchar('>');
  putchar(' ');
  char buff[100];
  gets_s(buff, sizeof(buff));
  puts(buff);
  return 0;
}

しかし、こんなエラーが出てきてしまいます。実は、gets_s は C11 では optional で必ずしも実装する必要がないようです。(実際に実装されていない) ・・・というオチでした。

src/gets_s.c: In function ‘main’:
src/gets_s.c:9:3: warning: implicit declaration of function ‘gets_s’ [-Wimplicit-function-declaration]
   gets_s(buff, sizeof(buff));
   ^
 /tmp/ccmaAfSy.o: In function `main':
gets_s.c:(.text+0x44): undefined reference to `gets_s'
collect2: error: ld returned 1 exit status

gets はどうかというとこんなプログラムで試してみました。

#include <stdio.h>

//  char *gets(char *);

void main() {
  putchar('>');
  putchar(' ');
  char buff[80];
  gets(buff);
  puts(buff);
}

やはり、-std=c11 を付けるとエラーになりますね。

ubuntu@ip-172-31-11-130:~/workspace/C/C11$ gcc -std=c11 -o bin/gets src/gets.c
src/gets.c: In function ‘main’:
src/gets.c:9:3: warning: implicit declaration of function ‘gets’ [-Wimplicit-function-declaration]
   gets(buff);
   ^
/tmp/cc9Eiixi.o: In function `main':
gets.c:(.text+0x38): warning: the `gets' function is dangerous and should not be used.

-std=c11 なしだと警告は出ますが、一応コンパイルは通り実行もできました。

ubuntu@ip-172-31-11-130:~/workspace/C/C11$ gcc -o bin/gets src/gets.c
src/gets.c: In function ‘main’:
src/gets.c:5:3: warning: ‘gets’ is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]
   gets(buff);
   ^
/tmp/ccRzQYd0.o: In function `main':
gets.c:(.text+0x1f): warning: the `gets' function is dangerous and should not be used.
ubuntu@ip-172-31-11-130:~/workspace/C/C11$ bin/gets
abc
abc

fgets

gets_s の代替ですが、fgets を使うのがよさそうです。これだと、エラーも警告も出ませんし、もちろん実行も問題ありません。ところで、本来の ANSI C ではコメントは /* .. */ のみで、// は使えません。また、変数宣言は実行文の前に置かなくてはいけません。このサンプルはそうでなくてもちゃんとコンパイルされるので、C11 仕様でコンパイルされていることがわかります。

#include <stdio.h>

// char* fgets(char* ,int, FILE*);

int main(int argc, char *argv[]) {
  putchar('>');
  putchar(' ');
  char buff[100];
  fgets(buff, sizeof(buff), stdin);
  fputs(buff, stdout);
  return 0;
}

 

K & R は使えるの ?

K & R 仕様のソースなんてもう見かけませんが、「-traditional というスイッチでコンパイルできる」とどこかに書いてありました。試しにやってみましたがダメでした。もうその情報が自体が古いのかもしれません。あるいはプログラムが間違っているのか。追及してもしょうがないので話はここまで。

#include <stdio.h>

/* Kernighan & Ritchie */

int main(argc, argv)
int argc;
char *argv[];
{
  puts("Hello world.");
}
ubuntu@ip-172-??-??-130:~/workspace/C/C11$ gcc -traditional -o bin/ritchie src/ritchie.c
gcc: error: GNU C no longer supports -traditional without -E
ubuntu@ip-172-??-??-130:~/workspace/C/C11$ gcc -traditional -E -o bin/ritchie src/ritchie.c
In file included from /usr/include/features.h:375:0,
              from /usr/include/stdio.h:28,
              from src/ritchie.c:2:
 /usr/include/x86_64-linux-gnu/sys/cdefs.h:30:3: error: #error "You need a ISO C conforming compiler to use the glibc headers"
 # error "You need a ISO C conforming compiler to use the glibc headers"
   ^

 

C11 (C99 で追加された仕様を含む) のトピック

この内容については Build Insider というサイトに詳しい説明があります。これらの機能が実際に使えるかどうかは、処理系 (C コンパイラ) の実装状況に依存します。つまり 100% 対応しているとは限りません。


このうち、文法的な内容で自分が使いそうなものを試してみました。


コメントに // が使える。

これは ANSI-C では /* ... */ しか使えなかったのが C99 で使えるようになったというものですが、実際には各処理系 (C コンパイラ) で独自に対応していて、かなり前から特に意識せずに使っていたかもしれません。これのサンプルは、次の「現在の関数名を与えるマクロ __func__ が使える。」といっしょに示します。


現在の関数名を与えるマクロ __func__ が使える。(C99)

これは、現在の関数名を与えるマクロとして __func__ が追加されたということで、エラーメッセージなどに活用できます。同様なものに __FILE__, __LINE__ がありますが、これらは ANSI-C から使えるそうです。


#include <stdio.h>
// コメントに // が使える。
// 現在の関数名を与えるマクロ __func__ が使える。
int main(int argc, char* argv[]) {
   printf("%s\n", __FILE__);
   printf("%d\n", __LINE__);
   printf("%s\n", __func__);
   return 0;
}

実行例

_func_.c
7
main


匿名構造体や共有体 (C11)

名前なしの構造体や共有体が使えるようになったようです。整数で言えば変数に整数リテラルを代入できますが、構造体変数に構造体リテラルを代入できるイメージです。


サンプル

#include <stdio.h>

typedef struct {
   double x;
   double y;
} Location;

// C11 匿名構造体や共有体
int main(int argc, char* argv[]) {
   // 匿名構造体を p に代入する。
   Location p = {
      .x = 212.1,
      .y = -26.9
   };
  
  printf("%10.2f %10.2f\n", p.x, p.y);
}

実行例

212.10     -26.90

配列の初期サイズの指定に変数が使える。

ANSI C では配列宣言でサイズの指定は定数である必要がありましたが、C99 からは変数も使えるようになりました。ただし、リストのように伸縮はできません。あくまで、宣言のとき1回だけサイズ指定ができるだけです。

サンプル

#include <stdio.h>

/*
 *  配列の初期サイズの指定に変数が使える。(伸縮はできない)
*/
int main(int argc, char* argv[]) {
   int n = 5;
   char ary[n];
   printf("%d\n", sizeof(ary));
   return 0;
}

実行例

5

stdbool.h をインクルードすると bool, true, false が使える。

ANSI C ではブール型というのはなくて、マクロで整数などをブール型などのように扱っていました。C99 からは stdbool.h をインクルードすると bool, true, false が使えるようになりました。これは、-std=c11 コンパイラオプションを指定しなくても stdbool.h をインクルードするだけで使えます。

サンプル

#include <stdbool.h>
#include <stdio.h>

/*
*  stdbool.h をインクルードすると bool, true, false が使える。
*/
int main(int argc, char* argv[]) {
   bool b = false;
   printf("%d\n", b);

   b = true;
   printf("%d\n", b);
   return 0;
}

実行例

0
1

変数宣言はブロックの先頭でなくてもよい

ANSI-C では変数宣言は必ずブロック(関数など)の先頭でまとめて行わないとエラーになりました。C99 からは途中で宣言してもエラーになりません。

サンプル

#include <stdio.h>

// 変数宣言は ANSI C では関数の先頭で行う必要があったが、C11 (C99) では途中でもよい。
int main(int argc, char* argv[]) {
   for (int i = 0; i < 10; i++) {
      printf("%d\n", i);
   }
   return 0;
}

実行例

0
1
2
3
4
5
6
7
8
9

構造体、共用体の初期化子

これは前に出てきた匿名構造体と似ていますが、特定のメンバーだけ初期化できたりします。下のサンプルでは、y だけ初期化しています。初期化指定がない x は既定値の 0 になります。

サンプル

#include <stdio.h>

typedef struct {
  double x;
  double y;
} Location;

// 構造体、共用体の初期化子
int main(int argc, char* argv[]) {
  Location p = { .y = -150.0 };
  printf("%10.3f %10.3f\n", p.x, p.y);
  return 0;
}

実行例

0.000   -150.000

inline 関数

C++ の inline と似た機能ですが、自分の環境では正しく動作しませんでした。

サンプル

#include <stdio.h>

// C11 inline

inline int compare(double x, double y) {
  if (x > y)
    return 1;
  else if (x == y) 
    return 0;
  else
    return -1;
}

int main(int argc, char* argv[]) {
  printf("%d\n", compare(1, -1));
  printf("%d\n", compare(1, 1));
  printf("%d\n", compare(0, 1));
  return 0;
}

複合リテラル

これは構造体や共用体のリテラルです。構造体、共用体の初期化子などと似ていますが、文法がちょっと変わっています。つまりかっこで型を指定します。

サンプル

#include <stdio.h>

typedef struct {
  double x;
  double y;
} Location;

Location fncLoc();

// 複合リテラル
int main(int argc, char* argv[]) {
   Location p = fncLoc();
   printf("%10.3f %10.3f\n", p.x, p.y);
   return 0;
}

Location fncLoc() {
  // Location の複合リテラル
  return (Location) { .x = 150.0, .y = -150.0 };
}

実行例

150.000   -150.000

整数幅の指定

ANSI-C では整数の幅はマシン環境などに依存して最適なものが選ばれましたが、C99 からは stdint.h をインクルードすることで、整数の指定が細かにできるようになりました。それに伴い、整数の最大最小値のマクロが追加されました。

サンプル

#include <stdio.h>
#include <stdint.h>

// 整数の幅
int main(int argc, char* argv[]) {
  int64_t n64; // 64bit 整数
  intmax_t n;  // その環境での最大幅の整数

  n64 = 1111111111111111L;
  printf("%ld\n", n64);
  n = INTMAX_MIN;
  printf("%ld\n", n);
  return 0;
}

実行例

1111111111111111
-9223372036854775808

バッファの長さを指定できる snprintf 関数

sprintf はバッファのサイズを指定できないので、変換結果がバッファからはみ出して他のデータを壊す可能性がありました。snprintf はバッファ長を指定してその長さを超えないようになっています。

サンプル

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

// snprintf のテスト。snprintf はバッファの長さを指定できる。
int main(int argc, char* argv[]) {
   char buff[6];

   memset(buff, '\0', sizeof(buff));
   snprintf(buff, sizeof(buff), "%d", 987654321);
   printf("%s\n", buff);

   memset(buff, '\0', sizeof(buff));
   snprintf(buff, sizeof(buff), "%d", 12345);
   printf("%s\n", buff);
   return 0;
}

実行例

98765
12345

 

 


 

 開設 2014年12月   著作権 2014-2016 bonk.red  連絡先: こちらからメッセージを送ってください(お仕事も大募集)