C でポインタから配列のサイズを取得するにはどうすればよいですか?
-
04-07-2019 - |
質問
の「配列」を割り当てました mystruct
サイズの n
このような:
if (NULL == (p = calloc(sizeof(struct mystruct) * n,1))) {
/* handle error */
}
後でのみアクセスできるようになります p
, 、そしてもうありません n
. 。ポインタだけを指定して配列の長さを決定する方法はありますか p
?
わかりました しなければならない 可能です、なぜなら free(p)
それだけを行います。知っている malloc()
割り当てられたメモリ量を追跡しており、そのため長さがわかります。おそらくこの情報をクエリする方法はあるでしょうか?何かのようなもの...
int length = askMallocLibraryHowMuchMemoryWasAlloced(p) / sizeof(mystruct)
コードを作り直せばいいのはわかっていますが、 n
, でもできればやめたほうがいいです。何か案は?
解決
いいえ、 malloc
の実装の詳細に強く依存しない限り、この情報を取得する方法はありません。特に、 malloc
は、要求よりも多くのバイトを割り当てる場合があります(たとえば、特定のメモリアーキテクチャの効率のため)。 n
を明示的に追跡できるように、コードを再設計することをお勧めします。代替案は、少なくとも再設計とはるかに危険なアプローチ(少なくとも)です(非標準であり、ポインターのセマンティクスを乱用し、後継者にとってはメンテナンスの悪夢です): mallocされたアドレスのlength n
の後に配列が続きます。割り当ては次のようになります。
void *p = calloc(sizeof(struct mystruct) * n + sizeof(unsigned long int),1));
*((unsigned long int*)p) = n;
n
は *((unsigned long int *)p)
に保存され、配列の開始は
void *arr = p+sizeof(unsigned long int);
編集:悪魔の擁護者を演じるためだけに...これらの「解決策」を知っています。すべて再設計が必要ですが、試してみましょう。 もちろん、上記のソリューションは、(十分に詰め込まれた)構造体のハックな実装にすぎません。以下を定義することもできます:
typedef struct {
unsigned int n;
void *arr;
} arrInfo;
生のポインタではなく、 arrInfo
を渡します。
調理中です。しかし、あなたが再設計している限り、なぜここに止まるのですか?本当に必要なのは、抽象データ型(ADT)です。アルゴリズムとデータ構造クラスの紹介テキストはそれを行います。 ADTは、データ型のパブリックインターフェイスを定義しますが、そのデータ型の実装を隠します。したがって、公開されている配列のADTは次のようになります
typedef void* arrayInfo;
(arrayInfo)newArrayInfo(unsignd int n, unsigned int itemSize);
(void)deleteArrayInfo(arrayInfo);
(unsigned int)arrayLength(arrayInfo);
(void*)arrayPtr(arrayInfo);
...
言い換えれば、ADTはデータと動作のカプセル化の形式です...つまり、ストレートCを使用してオブジェクト指向プログラミングに到達するのとほぼ同じです。 C ++コンパイラがないため、完全に独り占めして、STL std :: vector
を使用することもできます。
そこで、Cについて簡単な質問をして、C ++になりました。神は私たちすべてを助けます。
他のヒント
自分で配列サイズを追跡します。 freeはmallocチェーンを使用して、割り当てられた block を解放します。ブロックは、要求した配列と同じサイズである必要はありません
以前の回答を確認するために:ポインタを調べるだけでは、このポインタを返したmallocによって割り当てられたメモリ量を知る方法はありません。
それが機能したらどうなりますか
これが不可能な理由の一例。ポインターに割り当てられたメモリを返すget_size(void *)という仮想関数を使用したコードを想像してみましょう。
typedef struct MyStructTag
{ /* etc. */ } MyStruct ;
void doSomething(MyStruct * p)
{
/* well... extract the memory allocated? */
size_t i = get_size(p) ;
initializeMyStructArray(p, i) ;
}
void doSomethingElse()
{
MyStruct * s = malloc(sizeof(MyStruct) * 10) ; /* Allocate 10 items */
doSomething(s) ;
}
なぜ機能したとしても、それでも機能しないのですか?
ただし、このアプローチの問題は、Cではポインター演算を使用できることです。 doSomethingElse()を書き直しましょう:
void doSomethingElse()
{
MyStruct * s = malloc(sizeof(MyStruct) * 10) ; /* Allocate 10 items */
MyStruct * s2 = s + 5 ; /* s2 points to the 5th item */
doSomething(s2) ; /* Oops */
}
関数に有効なポインタを送信しましたが、mallocによって返されたポインタではないため、get_sizeがどのように機能するかを想定しています。また、get_sizeがサイズを見つけるためにすべての問題を(つまり、非効率的な方法で)行ったとしても、この場合、コンテキストで間違っている値を返します。
結論
この問題を回避する方法は常にあり、Cではいつでも独自のアロケータを記述できますが、割り当てられたメモリの量を覚えるだけで十分な場合は、おそらく面倒です。
一部のコンパイラは、msize()または同様の関数(_msize()など)を提供します。
ひどい方法をお勧めできますか?
すべての配列を次のように割り当てます。
void *blockOfMem = malloc(sizeof(mystruct)*n + sizeof(int));
((int *)blockofMem)[0] = n;
mystruct *structs = (mystruct *)(((int *)blockOfMem) + 1);
その後、いつでも int *
に配列をキャストし、-1番目の要素にアクセスできます。
配列ポインター自体ではなく、そのポインターを free
してください!
また、これは恐ろしいバグを引き起こし、髪を引き裂く可能性があります。おそらく、API呼び出しなどでalloc funcsをラップできます。
mallocは、少なくとも要求された大きさ以上のメモリブロックを返します。したがって、ブロックサイズを照会できたとしても、配列サイズは確実に得られません。そのため、コードを変更して自分で追跡する必要があります。
ポインターの配列には、NULLで終わる配列を使用できます。この長さは、文字列で行われるように決定できます。この例では、構造属性を使用してマークしてから終了することができます。もちろん、NULLにできないメンバーがいるかどうかによって異なります。配列内のすべての構造体に設定する必要がある属性名があるとします。次に、次の方法でサイズを照会できます。
int size;
struct mystruct *cur;
for (cur = myarray; cur->name != NULL; cur++)
;
size = cur - myarray;
ところで、この例ではcalloc(n、sizeof(struct mystruct))でなければなりません。
他の人は、プレーンなcポインターの制限と、 malloc()
の stdlib.h
実装について議論しています。一部の実装では、要求されたサイズよりも大きい allocated ブロックサイズを返す拡張機能を提供しています。
この動作が必要な場合 は、専用のメモリアロケータを使用または作成できます。この最も簡単なことは、 stdlib.h
関数のラッパーを実装することです。次のようなもの:
void* my_malloc(size_t s); /* Calls malloc(s), and if successful stores
(p,s) in a list of handled blocks */
void my_free(void* p); /* Removes list entry and calls free(p) */
size_t my_block_size(void* p); /* Looks up p, and returns the stored size */
...
本当にあなたの質問は-「mallocされた(またはcallocされた)データブロックのサイズを調べることができます」です。そして、他の人が言ったように:いいえ、標準的な方法ではありません。
ただし、それを行うカスタムmalloc実装があります-たとえば、 http://dmalloc.com/
方法はわかりませんが、mallocの内部をいじくり回すことに対処すると想像できますが、これは一般的に非常に悪い考えです。
割り当てたメモリのサイズを保存できないのはなぜですか?
編集:コードを修正する必要があることがわかっているので、nがわかるようにします。はい、mallocをポーリングするのは迅速かつ簡単かもしれませんが、nを確認することで混乱を最小限に抑え、設計を強化できます。
malloc ライブラリにブロックのサイズを問い合わせることができない理由の 1 つは、アロケーターは通常、最小粒度要件 (たとえば、16 バイト) を満たすためにリクエストのサイズを切り上げるためです。したがって、5 バイトを要求すると、サイズ 16 のブロックが返されます。16 を 5 で割ると、実際には 1 つの要素しか割り当てられていないにもかかわらず、3 つの要素が得られます。そもそも要求したバイト数を malloc ライブラリが追跡するには余分なスペースが必要になるため、自分でそれを追跡するのが最善です。
これは私のソートルーチンのテストです。 float値を保持するために7つの変数を設定し、それらを最大値を見つけるために使用される配列に割り当てます。
魔法はmyMaxの呼び出しにあります:
float mmax = myMax((float *)& arr、(int)sizeof(arr)/ sizeof(arr [0]));
それは魔法でしたね。
myMaxはfloat配列ポインター(float *)を想定しているため、& arrを使用して配列のアドレスを取得し、floatポインターとしてキャストします。
myMaxは、配列内の要素数をintとしても期待しています。 sizeof()を使用して、配列のサイズと配列の最初の要素を取得し、合計バイトを各要素のバイト数で除算することにより、その値を取得します。 (intのサイズはシステムによっては2バイト、OS X Macのようなシステムでは4バイトであり、他のシステムでは他のものになる可能性があるため、intのサイズを推測したり、ハードコーディングしたりしないでください。)
注:データにさまざまな数のサンプルがある場合、これらはすべて重要です。
テストコードは次のとおりです。
#include <stdio.h>
float a, b, c, d, e, f, g;
float myMax(float *apa,int soa){
int i;
float max = apa[0];
for(i=0; i< soa; i++){
if (apa[i]>max){max=apa[i];}
printf("on i=%d val is %0.2f max is %0.2f, soa=%d\n",i,apa[i],max,soa);
}
return max;
}
int main(void)
{
a = 2.0;
b = 1.0;
c = 4.0;
d = 3.0;
e = 7.0;
f = 9.0;
g = 5.0;
float arr[] = {a,b,c,d,e,f,g};
float mmax = myMax((float *)&arr,(int) sizeof(arr)/sizeof(arr[0]));
printf("mmax = %0.2f\n",mmax);
return 0;
}
uClibc では、 malloc.h
:
/* The size of a malloc allocation is stored in a size_t word
MALLOC_HEADER_SIZE bytes prior to the start address of the allocation:
+--------+---------+-------------------+
| SIZE |(unused) | allocation ... |
+--------+---------+-------------------+
^ BASE ^ ADDR
^ ADDR - MALLOC_HEADER_SIZE
*/
/* The amount of extra space used by the malloc header. */
#define MALLOC_HEADER_SIZE \
(MALLOC_ALIGNMENT < sizeof (size_t) \
? sizeof (size_t) \
: MALLOC_ALIGNMENT)
/* Set up the malloc header, and return the user address of a malloc block. */
#define MALLOC_SETUP(base, size) \
(MALLOC_SET_SIZE (base, size), (void *)((char *)base + MALLOC_HEADER_SIZE))
/* Set the size of a malloc allocation, given the base address. */
#define MALLOC_SET_SIZE(base, size) (*(size_t *)(base) = (size))
/* Return base-address of a malloc allocation, given the user address. */
#define MALLOC_BASE(addr) ((void *)((char *)addr - MALLOC_HEADER_SIZE))
/* Return the size of a malloc allocation, given the user address. */
#define MALLOC_SIZE(addr) (*(size_t *)MALLOC_BASE(addr))