不明瞭なC 99配列特徴

10390 ワード

導入


C99 配列の新機能をいくつか紹介します.C 99は20歳以上ですが、あなたはめったに野生で使用されるこれらの新機能を参照してください.そのため、あなたはそれらに精通していないでしょう、そして、あなた自身のコードでそれらを使用しないようにより少ないです.そこで、これらの機能のツアーです.

フレキシブル配列メンバー


c 99の最後のメンバであるstruct つ以上の名前付きメンバには、指定されていないサイズの配列があります.
struct s {
    size_t n;
    double d[];            // Flexible Array Member
};
通常、そのようなstruct おそらく、ディスクから読み込んだバイナリファイルを含んでいるメモリのより大きい領域のための「ヘッダー」として役立つ.
配列がどれくらい大きいかをどうにか覚えておくことはあなたのコード次第です.(これはもちろん、struct .)
sizeof などに適用されるstruct , 配列が存在しないかのようです.
あなたがそのようなものを持つ間struct スタックでは、配列に対してサイズが設定されていないので便利ではありません(したがって、配列へのアクセスは未定義です).役に立つstruct ヒープに割り当てなければなりません.
struct s *ps = malloc( sizeof(struct s) + sizeof(double[n]) );
最後に、その中の割り当てstruct sは配列をコピーしません(コンパイラがどれくらい大きいかわからないので).
struct s *ps1, *ps2;
// ...
*ps1 = *ps2;               // copies only 'n' member
ちなみにC +はC 99からの柔軟な配列メンバーを採用しませんでした.

可変長配列


C 99の前に、すべての配列は固定長(コンパイル時に知られている)であると宣言されなければなりませんでした.C 99で導入された、可変長配列(VLASは、柔軟な配列メンバーと混同されない)は可変長(宣言されるまで実行時まで)であると宣言できます.例えば、
void f( size_t n ) {
    int a[n];
それだけでなくsizeof 演算子、歴史的にコンパイル時の演算子は、時にはランタイム演算子-ときに引数がVLAです.
size_t sz = sizeof(a) / sizeof(a[0]); // sz = 10
(初代)sizeof は引数がVLAであるので、実行時に評価される二番目sizeof はまだコンパイル時に評価される.

可変長アレイ警告


VLASへの1つの重大な警告は、長さがあまりに大きいならば、それは静かにスタックをあふれるでしょう.加えてmalloc() 帰着NULL 失敗すると、VLAがスタックをオーバーフローしたときに検出する方法はありません.したがって、サイズが「あまりに大きい」ことがありえるならば、あなたのコードはそれに対して警戒しなければなりません:
size_t const A_LEN_MAX = 1024;
// ...

if ( n > A_LEN_MAX )
    // Do something else?
int a[n];
しかし、あなたがそれを知っているならばA_LEN_MAX 最大の安全サイズであり、次に宣言するだけでなくa その大きさでVLAを使わない.
ところで、小さなサイズの最適化を行うことができました.
void f( size_t n ) {
    int a[A_LEN_MAX];
    int *const p = n <= A_LEN_MAX ? a : malloc( n * sizeof(*p) );
    // use only 'p' to access array
    if ( p != a )
        free( p );
}
即ち、n 大きすぎると、スタック上の(固定サイズの)配列を使用しますそうでなければ、ヒープに動的に配列を使用します.これは、呼び出しに保存する利点がありますmalloc() and free() "小さな"n まだ"大きい"のために動作しますn . しかし、VLAが使用されていないことに注意してください.
したがって、モラルは:コンパイル時にサイズを知りませんが、それが「あまりに大きい」でないと保証することができるときだけ、VLASを使用してくださいしかし、これはほとんど真実ではない.
後に、Vlasは時々便利であると思われますが、C 11がVRASにオプションの特徴を作ったように、問題があります.ちなみにC +はC 99からVASを採用しなかった.

パラメータの配列構文


注意してくださいとして、配列の構文を使用して関数のパラメータを宣言することができますが、注意してくださいsyntactic sugar コンパイラはポインタなどのパラメータを書き換えるので、
void f( int a[] );         // int *a

Note that I’m intentionally writing “array syntax for parameters” and not “array parameters” because “array parameters,” despite appearances, simply don’t exist in C.


パラメータの配列構文を使用する唯一の潜在的な利点は、それが人間の読者に伝えることですa は少なくとも1つのint 正確にはなくint . しかし、NULLポインタでそのような関数を呼び出すことができるので、それは推定だけで、保証ではありません.
f( NULL );                 // f’s 'a' will be NULL
サイズを追加するのは役に立ちません.
void f( int a[10] );       // int *a
再び、これは人間の読者に伝えるかもしれないa は10の配列であると推定されるint sコンパイラはサイズを無視します.

Array syntax for parameters in C is a remnant of how pointers are declared in New B (the precursor to C). See The Development of the C Language, Dennis M. Ritchie, April, 1993.


パラメータの非NULL配列構文ポインタ


C 99で追加された機能の1つは、“配列”関数パラメータを宣言する能力で、NULLではなく、最小サイズでなければなりません.
void f( int a[static 10] );
どちらかをパスしようとするならNULL または10未満の配列int sコンパイラが警告します.

This marks yet another overloading of the static keyword in C since this static has nothing to do with either linkage or duration. Incidentally, C++ never adopted this syntax.
Also incidentally, C99 did not introduce a parallel way to specify that a pointer parameter must not be null.


パラメータの警告


関数パラメータの配列構文を使用することもできますdangerous :
void f( int a[10] ) {      // int *a
    for ( size_t i = 0; i < sizeof(a)/sizeof(*a); ++i ) {
        // ...
意図は配列のすべての要素を反復することですsizeof 式が配列の場合は正しい.a が、配列ではなくポインタである.したがって、ポインタのサイズは、int . 幸運にもgcc これについて警告します.

パラメータの修飾配列構文


配列構文で宣言されたパラメータが本当にポインタであることを家に帰るには、以下のように変更できます:
int ra[10];                // real array

void f( int pa[] ) {       // int *pa
    ++ra;                  // error (as expected)
    ++pa;                  // OK (surprisingly)
C 99はまた、書き直されたポインタを修飾する能力を追加しました.
void f( int pa[const] ) {  // int *const pa
    ++pa;                  // error now

In addition to const, you can also qualify the pointer with and restrict (CVR).


これらのどちらも
void f( int const pa[] );  // pointer to const int
void f( const int pa[] );  // same as above
と同じですconst 外に[]int ではなくpa .

Why doesn’t the compiler convert parameters with array syntax to const pointers? Because const wasn’t a part of C when Kernighan & Ritchie invented it.
Incidentally, C++ never adopted this syntax.


パラメータの可変長配列構文


C 99も機能パラメータ用にVLASを使用する機能を追加しました.
void f( size_t n, int a[n] ) { // int *a
すなわち、「配列」の大きさは、それに先行する積分パラメータによって与えられる.ただし、a はポインタである.ランタイムでサイズ情報があるにもかかわらず.sizeof(a) はポインタのサイズを返す.したがって、この機能は、人間の読者に伝えるためにのみ機能しますn は配列の推定サイズです
しかし、この「特徴」は、実際に多次元配列に有用です.しかし、その前に、関数パラメータの多次元配列構文についての迅速な再定義.

パラメータの多次元配列構文


理解しているように、配列構文を使用して、多次元配列の関数パラメータを宣言することもできます.
void f( int a[10][20] );   // int (*a)[20]
コンパイラが関数パラメータの配列構文をポインタに変換する規則は、最初の(最も左の)次元だけに起こります残りの次元(s)は“配列のネス”を保つゆえにa は20の実配列へのポインタですint s.

Note that the parentheses are necessary: without them, it would be an array of 20 pointers to int. FYI: to help decipher cryptic C declarations, you can use cdecl.


配列へのポインタは、配列の名前がその最初の要素へのポインタに「崩壊」するので、しばしばCプログラムで起こりません.ほとんどの場合、サイズ情報が失われても十分です.しかし、配列へのポインターは、配列のサイズを型の一部として保持します.したがって、異なるサイズの配列へのポインター間の割り当てを警告します.
int (*p3)[3];              // pointer to array 3 of int
int (*p5)[5];              // pointer to array 5 of int
p5 = p3;                   // warning: incompatible pointers
特に、
int a[10];
int *pi = a;               // pointer to int (via decay)
int (*pa)[10] = &a;        // pointer to array 10 of int
両方pi and pa メモリ内の同じ場所を指します.&a[0] ), しかし、“配列へのポインター”は配列の崩壊から生じるポインタとは全く別のものです.For pi , コンパイラは、それが指す配列のサイズを「忘れます」for pa , それはサイズを記憶します.
配列へのポインターがあまり使われていない理由の一部は、ポインタを最初に参照しなければならないので、配列要素にアクセスすることができないからです.
int e1 = (*p3)[1];         // must dereference p3 first
しかし、ポインタを一度別のポインタに辿ることでポインタを使用できます:
int *p = *p3;
int e1 = p[1];             // same as: (*p3)[1]

多次元VLAパラメータ


パラメータの多次元配列構文をVLASに使用できます.
void f( size_t m, size_t n, int a[m][n] ) {
コンパイラはポインタとして最初の配列ディメンションだけを書き換えるので、上記は本当にです:
void f( size_t m, size_t n, int (*a)[n] ) {
それでa は、n int この場合、VLAは実際には1970年代から有用な機能ですn コンパイラが配列の各行の長さを知ることができます.その上、sizeof (最初のものは、もう一度実行時演算子になります:
size_t sz = sizeof(*a) / sizeof(**a); // sz = n
一般的にVLASとは異なり、関数パラメータに使用されるVLASは、実際の配列が関数に渡されるので、安全です.
void f( size_t m, size_t n, int a[m][n] ) {
    // ...
}

void g( void ) {
    int a[10][20];
    f( 10, 20, a );
}
新しいVLAはここで実行時に作成されないので、スタックをオーバーフローすることはできません.

多次元VLAパラメータ宣言


関数を宣言する際には、パラメータ名を省略することができます.しかし、あなたがそうするならば、VLAのサイズを指定する名前がありません;しかし、C 99はこの場合の新しい構文を追加しました.
void f( size_t, size_t, int[][] );    // error
void f( size_t, size_t, int[*][*] );  // OK
つまり、あなたは* 名前付きのサイズのVLAを示す.最初の配列ディメンションは常にポインタに変換されるので注意してください* は2番目のディメンションからのみ必要です.
void f( size_t, size_t, int[][*] );   // same as previous
したがって、あなたが必要* シングルディメンション配列構文を使用する場合.

結論


C 99の新しい配列の特徴を追加しました.
  • 柔軟な配列メンバー.
  • VLAS (安全ではないので、使用しないでください).
  • 関数パラメータのVLAS(これは安全ですが、実際には多次元配列にのみ有用です).
  • static これにはパラメータのために非NULLで最小サイズの配列が渡される.
  • 能力const , volatile , or restrict 関数引数の配列から減衰した書き換えポインタを修飾します.