Wie funktioniert dieser Vorlagencode, um die Größe eines Arrays zu ermitteln?

61

Ich frage mich, warum diese Art von Code die Größe des Testarrays erhalten kann. Ich bin mit der Grammatik in der Vorlage nicht vertraut. Vielleicht könnte jemand die Bedeutung des Codes unter erklären template<typename,size_t>. Außerdem wird auch ein Referenzlink bevorzugt.

#define dimof(array) (sizeof(DimofSizeHelper(array)))
template <typename T, size_t N>
char(&DimofSizeHelper(T(&array)[N]))[N];

void InitDynCalls()
{
    char test[20];
    size_t n = dimof(test);
    printf("%d", n);
}

Schatten Unhold
quelle
Haben Sie so etwas wie n3337 über C ++ 11 gelesen ? Es sollte für Ihre Frage relevant sein! Haben Sie darüber nachgedacht, std::arrayoder std::vector...
Basile Starynkevitch
@ BasileStarynkevitch Das habe ich nicht gelesen. Der Code wird in einer Bibliothek eines Drittanbieters angezeigt. Ich möchte nur die Bedeutung herausfinden.
Shadow Fiend
Siehe auch norvig.com/21-days.html für einen nützlichen Einblick (und schauen Sie, wer der Autor dieser Seite ist).
Basile Starynkevitch
2
Sieht aus wie ein Duplikat von stackoverflow.com/questions/6106158/…
sharptooth
@ BasileStarynkevitch Ich verstehe die Relevanz dieses Links nicht.
Leichtigkeitsrennen im Orbit

Antworten:

86

Das ist wirklich schwer zu erklären, aber ich werde es versuchen ...

Zunächst dimofwird die Dimension oder Anzahl der Elemente in einem Array angegeben. (Ich glaube, "Dimension" ist die bevorzugte Terminologie in Windows-Programmierumgebungen).

Dies ist notwendig , da C++und Cnicht geben Sie eine native Möglichkeit , die Größe eines Arrays zu bestimmen.


Oft sizeof(myArray)wird angenommen, dass dies funktioniert, aber das gibt Ihnen tatsächlich die Größe des Speichers und nicht die Anzahl der Elemente. Jedes Element benötigt wahrscheinlich mehr als 1 Byte Speicher!

Als nächstes könnten sie es versuchen sizeof(myArray) / sizeof(myArray[0]). Dies würde die Größe im Speicher des Arrays ergeben, geteilt durch die Größe des ersten Elements. Es ist in Ordnung und wird häufig im CCode verwendet. Das Hauptproblem dabei ist, dass es zu funktionieren scheint, wenn Sie einen Zeiger anstelle eines Arrays übergeben. Die Größe eines Zeigers im Speicher beträgt normalerweise 4 oder 8 Bytes, obwohl das Element, auf das er zeigt, ein Array von 1000 Elementen sein kann.


Als Nächstes müssen Sie also C++Vorlagen verwenden, um etwas zu erzwingen, das nur für Arrays funktioniert und einen Compilerfehler für einen Zeiger ausgibt. Es sieht aus wie das:

template <typename T, std::size_t N>
std::size_t ArraySize(T (&inputArray)[N])
{
    return N;
}
//...
float x[7];
cout << ArraySize(x); // prints "7"

Die Vorlage funktioniert nur mit einem Array. Es wird der Typ (nicht wirklich benötigt, muss aber vorhanden sein, damit die Vorlage funktioniert) und die Größe des Arrays abgeleitet und dann die Größe zurückgegeben. Die Art und Weise, wie die Vorlage geschrieben wird, kann möglicherweise nicht mit einem Zeiger funktionieren.

Normalerweise können Sie hier aufhören, und dies ist in der C ++ Standard Libary als std::size.


Achtung: Hier unten gelangt es in das Gebiet der haarigen Sprachanwälte.


Das ist ziemlich cool, scheitert aber immer noch in einem obskuren Randfall:

struct Placeholder {
    static float x[8];
};

template <typename T, int N>
int ArraySize (T (&)[N])
{
    return N;
}

int main()
{
    return ArraySize(Placeholder::x);
}

Beachten Sie, dass das Array xwird deklariert , aber nicht definiert . Um eine Funktion (dh ArraySize) damit aufzurufen , xmuss definiert werden .

In function `main':
SO.cpp:(.text+0x5): undefined reference to `Placeholder::x'
collect2: error: ld returned 1 exit status

Sie können dies nicht verknüpfen.


Der Code, den Sie in der Frage haben, ist ein Weg, um das zu umgehen. Anstatt tatsächlich eine Funktion aufzurufen, deklarieren wir eine Funktion, die ein Objekt mit genau der richtigen Größe zurückgibt . Dann wenden wir den sizeofTrick an.

Es sieht so aus, als würden wir die Funktion aufrufen, aber es sizeofhandelt sich lediglich um ein Konstrukt zur Kompilierungszeit, sodass die Funktion nie aufgerufen wird.

template <typename T, size_t N>
char(&DimofSizeHelper(T(&array)[N]))[N];
^^^^ ^                               ^^^
// a function that returns a reference to array of N chars - the size of this array in memory will be exactly N bytes

Beachten Sie, dass Sie ein Array von einer Funktion nicht zurückgeben können, aber einen Verweis auf ein Array zurückgeben können.

Dann DimofSizeHelper(myArray)ist ein Ausdruck, dessen Typ ein Array auf N chars ist. Der Ausdruck muss eigentlich nicht ausführbar sein, ist aber zur Kompilierungszeit sinnvoll.

Daher sizeof(DimofSizeHelper(myArray))wird Ihnen zum Zeitpunkt der Kompilierung mitgeteilt, welche Größe Sie erhalten würden, wenn Sie die Funktion tatsächlich aufrufen würden. Auch wenn wir es eigentlich nicht nennen.

Austin Powers Cross-Eyed


Mach dir keine Sorgen, wenn dieser letzte Block keinen Sinn ergab. Es ist ein bizarrer Trick, einen bizarren Randfall zu umgehen. Aus diesem Grund schreiben Sie diese Art von Code nicht selbst und lassen Bibliotheksimplementierer sich über diese Art von Unsinn Gedanken machen.

BoBTFish
quelle
3
@ Shadowfiend Es ist auch falsch. Die Dinge sind noch hässlicher, weil es eigentlich keine Deklaration einer Funktion ist, sondern eine Deklaration einer Funktionsreferenz ... Ich überlege immer noch, wie ich das möglicherweise erklären kann.
BoBTFish
5
Warum ist es eine Deklaration einer Funktionsreferenz? Das "&" vor "DimofSizeHelper" bedeutet, dass der Rückgabetyp char (&) [N] ist, entsprechend der Antwort von bolov.
Shadow Fiend
3
@ Shadowfiend Absolut richtig. Ich habe nur Müll geredet, weil ich mein Gehirn zu einem Knoten zusammengebunden habe.
BoBTFish
Dimension ist nicht die Anzahl der Elemente in einem Array. Das heißt, Sie haben möglicherweise Arrays mit 1, 2, 3 oder höheren Dimensionen, die jeweils die gleiche Anzahl von Elementen haben können. ZB Array1D [1000], Array 2D [10] [100], Array3D [10] [10] [10]. jeweils mit 1000 Elementen.
Jamesqf
1
@jamesqf In Sprachen wie C ++ ist ein mehrdimensionales Array einfach ein Array, das andere Arrays enthält. Aus Sicht des Compilers hängt die Anzahl der Elemente im primären Array häufig nicht mit seinem Inhalt zusammen - dies können sekundäre oder tertiäre Arrays sein.
Phlarx
27
template <typename T, size_t N>
char(&DimofSizeHelper(T(&array)[N]))[N];

// see it like this:
//                char(&DimofSizeHelper(T(&array)[N]))[N];
// template name:       DimofSizeHelper
// param name:                             array
// param type:                          T(&     )[N])
// return type:   char(&                             )[N];

DimofSizeHelperist eine Vorlagenfunktion, die einen T(&)[N]Parameter verwendet - auch bekannt als Referenz auf ein C-Array von N Elementen vom Typ - Tund char (&)[N]eine Referenz auf ein Array von N Zeichen zurückgibt . In C ++ ein char ist Byte in der Verkleidung und sizeof(char)wird sein garantiert 1durch die Norm.

size_t n = dimof(test);
// macro expansion:
size_t n = sizeof(DimofSizeHelper(array));

nwird die Größe des Rückgabetyps von zugewiesen DimofSizeHelper, der sizeof(char[N])welcher ist N.


Das ist etwas verworren und unnötig. Der übliche Weg war:

template <class T, size_t N>
/*constexpr*/ size_t sizeof_array(T (&)[N]) { return N; }

Seit C ++ 17 ist dies ebenfalls nicht std::sizeerforderlich , da wir dies tun , jedoch auf allgemeinere Weise, um die Größe eines beliebigen stl-Containers ermitteln zu können.


Wie von BoBTFish hervorgehoben, ist dies für einen Randfall erforderlich.

Bolov
quelle
2
Dies ist erforderlich, wenn Sie das Array, dessen Größe Sie annehmen möchten, nicht ODR-verwenden können (es ist deklariert, aber nicht definiert). Zugegeben, ziemlich dunkel.
BoBTFish
Vielen Dank, dass Sie den Typ in der Vorlagenfunktion erklärt haben. Das hilft wirklich.
Shadow Fiend
3
Wir haben std::extentseit C ++ 11 die Kompilierungszeit.
LF