5

理解したい:

  • char[1]なぜCのaがchar*(なぜこれを行うのか?)として使用されることがあるのはなぜですか?
  • 内部の仕組み(何が起こっているのか)

次のサンプルプログラムを提供します。

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

struct test_struct {
    char *a;
    char b[1];
} __attribute__((packed)); ;

int main() {

    char *testp;
    struct test_struct test_s;

    testp = NULL;
    memset(&test_s, 0, sizeof(struct test_struct));

    printf("sizeof(test_struct) is: %lx\n", sizeof(struct test_struct));

    printf("testp at: %p\n", &testp);
    printf("testp is: %p\n", testp);

    printf("test_s.a at: %p\n", &test_s.a);
    printf("test_s.a is: %p\n", test_s.a);

    printf("test_s.b at: %p\n", &test_s.b);
    printf("test_s.b is: %p\n", test_s.b);

    printf("sizeof(test_s.b): %lx \n", sizeof(test_s.b));

    printf("real sizeof(test_s.b): %lx \n", ((void *)(&test_s.b) - (void *)(&test_s.a)) );

    return 0;
}

次の出力(OS X、64ビット)が表示されます。

sizeof(test_struct) is: 9
testp at: 0x7fff62211a98
testp is: 0x0
test_s.a at: 0x7fff62211a88
test_s.a is: 0x0
test_s.b at: 0x7fff62211a90
test_s.b is: 0x7fff62211a90
sizeof(test_s.b): 1 
real sizeof(test_s.b): 8 

メモリアドレスを見ると、構造体でさえ9バイトの大きさであり、16バイトが割り当てられていることがわかります。これは。が原因のようchar b[1]です。しかし、これらの余分なバイトが最適化/ memアライメントの理由で割り当てられたのか、それともCのchar配列の内部処理に関係しているのかはわかりません。

実際の例は次の場所で見ることができます<fts.h>

`man 3 fts`は、構造体メンバー`fts_name`を次のように表示します。

            char *fts_name;                 /* file name */

/usr/include/fts.hは、メンバーを次のように定義します。

            char fts_name[1];               /* file name */

結局、fts_name実際にはC文字列へのポインタとして使用できます。たとえば、printf("%s", ent->fts_name)作品を使ってstdoutに印刷します。

したがって、achar[1]が実際に1バイトの大きさである場合、64ビットマシンではメモリポインタとして使用できません。一方、これを本格的なものとして扱うこともchar *機能しません。上記の出力でわかるtest_s.b isように、NULLポインターが表示されるはずです...

4

2 に答える 2

2

Cがあなたの母国語でない場合、当然のことながら混乱します。最初に片付けることがいくつかあります。

Cでは、すべてのvar[n]手段は「で表されるアドレスを取得し、そのアドレスにバイトをvar追加n*sizeof(var's type)して、結果のアドレスを返すことです。また、C言語は、配列の宣言されたサイズを超えて歩くことを妨げません。

表示しているフォーマットは、より大きく、より重要な、可変長のメモリ割り当てにオーバーレイするように設計された構造の末尾によく見られます。このような構造では、以前の構造メンバーの1つにテールバッファスペースの実際の有効なバイトを指示させるのが通例です(通常は必須です)。

例:

typedef struct X
{
   unsigned int count;
   char data[1];
} X;

これは、アドレスを保持する変数にすぎないポインタメンバーを宣言することとは著しく異なります。

typedef struct Y
{
    unsigned int count;
    char *dataptr;
} Y;

Yで、アドレスをdataptr 保持します(アドレスも保持します)。Xでは、data アドレスです。

では、なぜこれを行うのですか?これを見てください。次のメモリダンプは、リトルエンディアン、1バイト構造のパッキング、および整数とポインタの両方のネイティブサイズが4バイトであることを前提としています。

0x00000000  0x10 0x00 0x00 0x00 0x01 0x02 0x03 0x04 
0x00000008  0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C
0x00000010  0x0D 0x0E 0x0F 0x10;

これに構造体Xをオーバーレイすると、次のようになります。

count : 16
data[] : { 0x01, 0x02, 0x03, ... 0x010 };

これにオーバーレイするstruct Yと、結果が著しく異なります。

count : 16
dataptr : 0x01020304

Cでは、配列の宣言されたサイズの終わりから手軽に(そして通常は悲劇的に)歩くことができることを忘れないでください。このオーバーレイ手法は、その機能を活用するだけです。ヘッドで占有されているメモリ領域について上記のことを考えるとstruct X、次のことができます。

struct X * pX = funcThatReturnsTheMemoryAddressAbove();
for (unsigned int i=0; i<pX->count; i++)
{
   do something with pX->data[i];
}

明らかに、このようなことを行うために管理メモリを割り当てる方法に注意する必要があります。

それが物事を完全に解決するのに役立つかどうかはわかりませんが、うまくいけばある程度です。

于 2012-10-12T17:18:56.030 に答える
2

これがそのトリックを説明する答えです。char[1]基本的に、malloc()構造体を作成するときに、追加の割り当てなしで文字列用のストレージをすでに確保するために、より多くのメモリを割り当てるという考え方があります。char something[0]同じ目的で使用されていることもあるので、直感的ではありません。

一方、これを本格的なchar *として扱うことも機能しません。これは、上記のtest_s.bの出力でわかるように、NULLポインターが表示されるはずです...

何かが配列である場合、その名前と&name、Cの配列の先頭へのポインターの両方を指定します。これは、構造体のメンバーであるか、独立変数であるかに関係なく機能します。

printf("real sizeof(test_s.b): %lx \n", ((void *)(&test_s.b) - (void *)(&test_s.a)) );

aこの行は、この構造体ではなく、に割り当てられたスペースのサイズを示しますb。後に何かを置きb、これを使って減算します。packed属性(コンパイラがアライメントなどを混乱させることを禁止することを意味します)を使用すると、1を取得する必要があります。

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

struct test_struct {
    char *a;
    char b[1];
    char c;
} __attribute__((packed));

int main() {
  struct test_struct s;
  printf("%lx\n", ((void*)&s.c) - ((void*)&s.b));
  return 0;
}

取得し1ます。

于 2012-10-12T17:06:18.480 に答える