4

先日これについて考えていたのですが、これが悪い考えなのか気になります... 文字列配列へのポインタを含む構造体があるとしましょう。memcpy() は、以下の例で「name」配列ポインターをコピーしますか? 編集: この例では std にアクセスできません。

struct charMap
{ 
  unsigned char * name;   
  unsigned char id;       
};
typedef struct charMap CharMapT;

class ABC
{
  public:
  ABC(){}
  void Function();
  CharMapT* structList;
}

void ABC::Function ()
{
  CharMapT list[] = 
  {
    {"NAME1", 1},
    {"NAME2", 2}, 
    {"NAME3", 3}
  };

  structList = new CharMapT[sizeof(list)];
  memcpy(structList, &list, sizeof(list));
}
4

5 に答える 5

6

提示されたコードにはいくつかのエラーがあります。最初にそれについて説明し、次にポインターと配列のストック ディアトライブについて説明します。

struct charMap
{ 
  unsigned int * name;   
  unsigned int id;       
};
typedef struct charMap CharMapT;

これは、最初のメンバー (名前) として unsigned int へのポインターを含み、2 番目のメンバー (id) として int を含む構造体型を宣言します。デフォルトのバイト パッキングを使用する 32 ビット システムでは、これは8 バイト幅になります (32 ビット ポインタ = 4 バイト、32 ビット符号付き int = 4 バイト)。これが 64 ビット マシンの場合、ポインタは 8 バイト幅になり、int はおそらく 32 ビット幅になり、構造体のサイズは12 バイトになります。

問題のあるコード

void ABC::Function ()
{
  CharMapT list[] = 
  {
    {"NAME1", 1},
    {"NAME2", 2}, 
    {"NAME3", 3}
  };

  structList = new CharMapT[sizeof(list)];
  memcpy(structList, &list, sizeof(list));
}

これにより、CharMapT 構造体の動的配列が割り当てられます。幾つか?あなたが思うよりももっと。は、配列sizeof(list)のバイト数を返します。list[]CharMapT 構造体は 8 バイト幅 (上記を参照) であるため、これは 3 * 8、つまり24 個の CharMapT アイテム(64 ビット ポインターを使用する場合は 36 個のアイテム) になります。

次に、 ( inは不要) からmemcpy()24バイト(または 36 バイト)を、新しく割り当てられたメモリに移動します。これにより、3 つの CharMapT 構造がコピーされ、割り当てた残りの 21 はそのまま残ります (最初のデフォルトの構築を超えて)。list&&list

注: aconst char *を として宣言されたフィールドに初期化しているunsigned int *ため、これをコンパイルしても基本的なデータ型は異なります。構造を修正してポインターの型を に変更するとconst char *、const データ セグメントのどこかにある静的文字列定数のアドレス ("NAME" 定数のアドレス) が、structList[0] の要素のポインター変数に割り当てられます。 .name、structList[2].name、および structList[3].name です。

これは、 が指すデータをコピーしません。ポインタ値のみをコピーします。データのコピーが必要な場合は、それらを生で割り当てる必要があります (malloc、new など)。

さらに良いのは、 を使用しstd::vector<CharMapT>std::stringforCharMapT::nameを使用std::copy()し、 ソースをレプリケートするために使用します (または直接割り当ても可能です)。

あなたが探していたものを説明してくれることを願っています。


ポインター vs. アレイ Diatribe

ポインターと配列を混同しないでください。ポインタは、アドレスを保持する変数です変数が整数値を保持するか、変数が文字型を保持するように、ポインターに保持される値はアドレスです。intchar

配列が異なります。これは (明らかに) 変数でもありますが、左辺値にすることはできず、通常使用されるほぼすべての場所で変換が行われます。概念的には、その変換により、配列のデータ型を指す一時ポインターが生成され、最初の要素のアドレスが保持されます。その概念が起こらない場合があります(address-of 演算子の適用など)。

void foo(const char * p)
{
}

char ar[] = "Hello, World!";
foo(ar); // passes 'ar', converted to `char*`, into foo. 
         // the parameter p in foo will *hold* this address

またはこれ:

char ar[] = "Goodbye, World!";
const char *p = ar;  // ok. p now holds the address of first element in ar
++p;                 // ok. address in `p` changed to address (ar+1)

しかし、これではありません:

char ar[] = "Goodbye, World!";
++ar; //  error. nothing to increment.
于 2012-11-27T21:10:21.143 に答える
4

が指す実際のデータはコピーされませんname。ポインターがコピーされ、2 つのオブジェクトの同じ場所への 2 つのポインターが作成されます (2 つの配列内のオブジェクトのペアごとに)。

于 2012-11-27T20:39:16.897 に答える
3

ここで本当に知っておく必要があるmemcpyのは、オリジナルのコピーが少しずつ得られるということだけです。したがって、同じデータを参照する同じ値 (つまり、アドレス) を持つ 2 つのポインターが得られます。

name余談ですが、 へのポインタとして宣言しましたがint、これはもちろん間違っています。である必要がありconst char*ます。また、これは C ではなく C++ であるため、いつか複雑な型になったstd::copy場合にコードを微妙に壊さないようなものの方が適しています。charMap同じメモで、ほとんどの状況でstd::stringの代わりに優先します。const char*

于 2012-11-27T20:40:13.260 に答える
0

sizeof()を呼び出すときの の使用は間違っていnewます。CharMapT要素の配列を割り当てています。要素数を指定する必要がありますが、代わりにバイト数を指定しています。したがって、それを修正する必要があります:

structList = new CharMapT[sizeof(list) / sizeof(CharMapT)];

その修正により、含まれる生データの正確なコピーが含まmemcpy()れるという結果になります。つまり、ポインターにはポインターと同じ値が含まれるため、それらはすべて文字列値の同じ物理メモリを指します。structListlist[]structList[N].namelist[N].name

文字列値のディープ コピーを実行する場合は、個別に割り当てる必要があります。たとえば、次のようになります。

void ABC::Function ()
{
    CharMapT list[] = 
    {
        {"NAME1", 1},
        {"NAME2", 2}, 
        {"NAME3", 3}
    };

    int num = sizeof(list) / sizeof(CharMapT);
    structList = new CharMapT[num];
    for (int i = 0; i < num; ++i)
    {
        int len = strlen(list[i].name);
        structList[i].name = new char[len+1];
        strcpy(structList[i].name, list[i].name);
        structList[i].name[len] = 0;

        structList[i].id = list[i].id;
    }
    ...
    for (int i = 0; i < num; ++i)
        delete[] structList[i].name;
    delete[] structList;
}
于 2012-11-27T21:05:01.517 に答える
0

@ EdS.の回答に追加したい:

次のようにすると、コードは c スタイルの c++ コードよりもはるかにc++になります。

#include<string>
#include<vector>

struct CharMap
{ 
  CharMap(const std::string& name, unsigned char id); // only needed if you don't use -std=c++11
  std::string name;   
  unsigned char id;       
};
CharMap::CharMap(const std::string& name, unsigned char id):
  name(name),
  id(id)
{}

class ABC
{
public:
  ABC(); // or ABC() = default; if you use -std=c++11
  void Function();
private:
  std::vector<CharMap> structList;
}

ABC::ABC(){} // not needed with -std=c++11

void ABC::Function ()
{
  // This works with -std=c++11:
  //structList = 
  //{
  //  {"NAME1", 1},
  //  {"NAME2", 2}, 
  //  {"NAME3", 3}
  //}; 

  // without c++11:
  structList = std::vector<CharMap>(3);
  structList[0] = CharMap("NAME1",1); // don't worry about copies, we have RVO (check wikipedia or SO)
  structList[1] = CharMap("NAME2",2);
  structList[2] = CharMap("NAME2",3);
}

std::vector配列の作成に使用しないのはなぜですか? 次のようにできます。

#include<vector>

std::vector<CharMapT> structList(list.size()); 

また、ポインターの使用を避けることで、sizeofオペレーターの誤った使用によるメモリ リークやバグの発生の可能性が減少します。

リストのメモリサイズと同じ数の要素を持つstructListは本当に必要ないと思います。(リストが double の場合、これはリスト内の要素数の何倍にもなる可能性があります。)

また、memcpyリストもベクトルである場合 (実際には ac 関数です)、実際には必要ありません。簡単な割り当て操作を行うだけです。

structList = list; // given that list is a vector.

これにより、memcpy などの要素がコピーされます。

于 2012-11-27T21:05:56.083 に答える