Cette question a été inspirée par une question similaire: Comment delete [] "connaît" la taille du tableau d'opérandes?
Ma question est un peu différente: Existe-t-il un moyen de déterminer la taille d'un tableau C++ par programme? Et sinon, pourquoi? Chaque fonction que j'ai vue qui prend un tableau nécessite également un paramètre entier pour lui donner la taille. Mais comme l'a souligné la question liée, delete[]
doit connaître la taille de la mémoire à désallouer.
Considérez ce code C++:
int* arr = new int[256];
printf("Size of arr: %d\n", sizeof(arr));
Cela imprime "Size of arr: 4
", qui est juste la taille du pointeur. Ce serait bien d'avoir une fonction qui imprime 256, mais je ne pense pas qu'une existe en C++. (Encore une fois, une partie de la question est pourquoi elle n'existe pas .)
Clarification : je sais que si j'ai déclaré le tableau sur la pile au lieu du tas (c'est-à-dire "int arr[256];
") que l'opérateur sizeof
retournerait 1024 (longueur du tableau * sizeof (int)).
delete []
Connaît la taille qui a été allouée. Cependant, cette connaissance réside dans le runtime ou dans le gestionnaire de mémoire du système d'exploitation, ce qui signifie qu'elle n'est pas disponible pour le compilateur pendant la compilation. Et sizeof()
n'est pas une fonction réelle, elle est en fait évaluée à une constante par le compilateur, ce qu'il ne peut pas faire pour les tableaux alloués dynamiquement, dont la taille n'est pas connue lors de la compilation.
Considérez également cet exemple:
int *arr = new int[256];
int *p = &arr[100];
printf("Size: %d\n", sizeof(p));
Comment le compilateur connaîtrait-il la taille de p
? La racine du problème est que les tableaux en C et C++ ne sont pas des objets de première classe. Ils se désintègrent en pointeurs, et il n'y a aucun moyen pour le compilateur ou le programme lui-même de savoir si un pointeur pointe vers le début d'un morceau de mémoire alloué par new
, ou vers un seul objet, ou vers un endroit au milieu d'un morceau de mémoire alloué par new
.
Une des raisons à cela est que C et C++ laissent la gestion de la mémoire au programmeur et au système d'exploitation, c'est pourquoi ils n'ont pas de récupération de place. L'implémentation de new
et delete
ne fait pas partie de la norme C++, car C++ est destiné à être utilisé sur diverses plates-formes, qui peuvent gérer leur mémoire de manières très différentes. Il peut être possible de laisser C++ garder une trace de tous les tableaux alloués et de leurs tailles si vous écrivez un traitement de texte pour une boîte Windows fonctionnant sur le dernier processeur Intel, mais cela peut être complètement irréalisable lorsque vous écrivez un système intégré fonctionnant sur un DSP.
Non, il n'y a aucun moyen de le faire en C++ standard.
Il n'y a pas vraiment de bonne raison à ce que je sache. Probablement, la taille a été considérée comme un détail d'implémentation et ne doit pas être exposée. Notez que lorsque vous dites malloc (1000), il n'y a aucune garantie que le bloc renvoyé est de 1000 octets --- seulement qu'il est au moins 1000 octets. Il s'agit très probablement d'environ 1020 (1K moins 4 octets pour la surcharge). Dans ce cas, la taille "1020" est la plus importante à retenir pour la bibliothèque d'exécution. Et bien sûr, cela changerait entre les implémentations.
C'est pourquoi le comité des normes a ajouté std: vector <>, qui garde une trace de sa taille exacte.
Eh bien, il existe en fait un moyen de déterminer la taille, mais ce n'est pas "sûr" et sera différent d'un compilateur à l'autre .... donc il ne devrait pas du tout être utilisé.
Lorsque vous le faites: int * arr = new int [256];
Le 256 n'est pas pertinent, vous recevrez 256 * sizeof (int) en supposant que dans ce cas 1024, cette valeur sera stockée probablement à (arr - 4)
Donc, pour vous donner le nombre "d'articles"
int * p_iToSize = arr - 4;
printf ("Nombre d'éléments% d", * p_iToSize/sizeof (int));
Pour chaque malloc, nouveau, quel que soit le bloc de mémoire continu que vous recevez, il y a également un espace réservé avec quelques informations concernant le bloc de mémoire qui vous a été attribué.
La façon courante de gérer cela est d'utiliser un vecteur
int main()
{
std::vector<int> v(256);
printf("size of v is %i capacity is %i\n", sizeof(int) * v.size(), sizeof(int) * v.capacity());
}
ou prédéfinir la taille
const int arrSize = 256;
int main()
{
int array[arrSize];
printf("Size of array is %i", sizeof(int) * arrSize);
}
Un peu de magie:
template <typename T, size_t S>
inline
size_t array_size(const T (&v)[S])
{
return S;
}
Et voici comment nous le faisons en C++ 11:
template<typename T, size_t S>
constexpr
auto array_size(const T (&)[S]) -> size_t
{
return S;
}
Selon votre application, vous pouvez créer une "valeur sentinelle" à la fin de votre tableau.
La valeur sentinelle doit avoir une propriété unique.
Vous pouvez ensuite soit traiter le tableau (ou effectuer une recherche linéaire) pour la valeur sentinelle, en comptant au fur et à mesure. Une fois que vous avez atteint la valeur sentinelle, vous avez votre nombre de tableaux.
Pour une chaîne C simple, le\0 de fin est un exemple de valeur sentinelle.
C++ a décidé d'ajouter new pour faire un malloc de typesafe, que new doit connaître les deux nombres e d'éléments pour appeler les ctors, donc supprimez pour appeler les dtors. Dans les premiers jours, vous devez réellement passer pour supprimer les numéros des objets que vous avez transmis à nouveau.
string* p = new string[5];
delete[5] p;
Cependant, ils pensaient que si vous utilisez un nouveau <type> [], les frais généraux d'un nombre étaient faibles. Ils ont donc décidé que le nouveau [n] devait se souvenir de n et le transmettre pour le supprimer. Il existe trois façons principales de le mettre en œuvre.
Peut-être est-il possible d'obtenir la taille comme ça:
size_t* p = new size_t[10];
cout << p[-1] << endl;
// Or
cout << p[11] << endl;
Ou l'enfer, rien de tout cela.
Il n'y a aucun moyen portable de déterminer la taille d'un tableau alloué dynamiquement en C++ étant donné uniquement son pointeur. C++ est conçu pour être très flexible et pour donner du pouvoir à l'utilisateur. Par exemple, la norme ne définit pas comment les allocateurs de mémoire doivent fonctionner, par ex. en ajoutant un en-tête de taille requise. Ne pas nécessiter d'en-tête permet une plus grande flexibilité.
À titre d'exemple, considérons une chaîne implémentée en tant que tableau char *. Il est courant d'utiliser des pointeurs au milieu du tableau pour sélectionner des sous-chaînes. À titre d'exemple, consultez la fonction strtok dans la bibliothèque C standard. Si un en-tête devait être incorporé juste avant chaque tableau, vous auriez besoin de jeter les parties du tableau avant la sous-chaîne.
Une autre façon de gérer les en-têtes serait d'avoir des en-têtes de tableau dans un bloc de mémoire et de les faire pointer vers la mémoire du tableau brut ailleurs. Dans de nombreuses situations, cela nécessiterait deux recherches de pointeur pour chaque référence, ce qui serait un gros frein aux performances. Il existe des moyens de surmonter ces lacunes, mais elles ajoutent de la complexité et réduisent la flexibilité de la mise en œuvre.
Le modèle std :: vector est ma façon préférée de conserver la taille d'un tableau lié au tableau lui-même.
C est un langage d'assemblage portable avec une meilleure syntaxe.
C'est parce que votre variable arr n'est qu'un pointeur. Il contient l'adresse d'un emplacement particulier en mémoire, sans rien en savoir. Vous le déclarez int *, ce qui donne au compilateur une indication de ce qu'il faut faire lorsque vous incrémentez le pointeur. En dehors de cela, vous pourriez pointer vers le début ou la fin du tableau ou dans la pile ou dans une mémoire non valide. Mais je suis d'accord avec toi, ne pas pouvoir appeler sizeof est très ennuyeux :)
QuantumPete
Vous ne pouvez pas, fondamentalement:
void foo(int* arr);
int arr[100] = {0};
foo(arr+1); // Calls foo with a pointer to 100-1 elements.
Un tableau C++ n'est rien de plus qu'une collection d'objets qui sont stockés dans une région de mémoire contiguë. Puisqu'il n'y a pas de trous entre eux (le rembourrage est à l'intérieur objets), vous pouvez trouver l'élément suivant d'un tableau en insérant simplement le pointeur. Au niveau du CPU, il s'agit d'un simple ajustement. C++ insère uniquement un multiplicateur sizeof (élément).
Notez que les implémentations peuvent choisir d'implémenter des "gros pointeurs" qui contiennent des limites de tableau. Ils devraient être deux fois plus gros, car vous auriez besoin d'un lien vers une sorte de "descripteur lié au tableau". En tant qu'effet secondaire, sur de telles implémentations, vous pourriez être en mesure d'appeler delete [] (1+new int[5]);
Malheureusement, ce n'est pas possible. En C et C++, il est de la responsabilité du programmeur de se souvenir de la longueur d'un tableau puisque la longueur du tableau n'est stockée nulle part. Delete [] et free () se souviennent de la taille du bloc alloué mais ils peuvent allouer plus de mémoire que demandé, de sorte que leurs structures de données internes stockant les tailles des blocs de mémoire alloués peuvent ne pas vous donner la taille exacte de votre tableau.
Notez que les vecteurs C++ STL, qui sont essentiellement des tableaux enveloppés dans une classe avec certaines fonctions d'assistance, stockent la longueur du tableau, donc si vous avez vraiment besoin de cette fonctionnalité, vous pouvez simplement utiliser des vecteurs.
En général, non. Les tableaux en C et C++ ne sont que des blocs de mémoire sans aucune information comptable attachée. Sans stocker la longueur du tableau en mémoire et sans ajouter de surcharge pour le faire, c'est impossible dans le cas général.
Il existe une exception pour les tableaux qui sont alloués statiquement. Par exemple, si vous déclarez: int a[50]
Alors sizeof(a)
fonctionnera. Ceci est possible car le [50] fait partie du type statique du tableau: il est connu du compilateur. sizeof est interprété au moment de la compilation.
Cependant, si vous créez un pointeur: int *p = a
, Alors sizeof(p)
renverra la taille du pointeur comme vous le mentionnez, pas la taille du tableau, car le compilateur ne sait pas quels points p à.
Non, il n'y a aucun moyen de le faire, vous devez garder une trace de sa taille en externe. Des classes comme std::vector
faites cela pour vous.
Il y a maintenant std :: array , un wrapper de compilation efficace autour d'un tableau de taille constante:
#include <array>
int main (int argc, char** argv)
{
std::array<int, 256> arr;
printf("Size of arr: %ld\n", arr.size());
}
Les paramètres sont <type, #elements>
.
Vous obtenez également quelques autres subtilités, comme les itérateurs, empty () et max_size ().
Existe-t-il un moyen de déterminer la taille d'un tableau C++ par programme? Et sinon, pourquoi?
@Dima,
Comment le compilateur connaîtrait-il la taille de p?
Le compilateur doit connaître la taille de p; sinon, il ne peut pas implémenter delete[]
. Le compilateur n'a pas besoin de dire à quelqu'un d'autre comment il le comprend.
Pour une façon amusante de vérifier cela, comparez le pointeur renvoyé par operator new[]
au pointeur renvoyé par new[]
.
Le compilateur ne peut pas savoir que
char *ar = new char[100]
est un tableau de 100 caractères car il ne crée pas un tableau réel en mémoire, il crée simplement un pointeur sur 100 octets non initialisés en mémoire.
Si vous voulez connaître la taille du tableau donné, utilisez simplement std :: vector. std :: vector est un meilleur tableau simplement.
Lorsque vous créez des pointeurs de tableau (créez un wrapper avec un modèle pour les pointeurs), vous ne pouvez pas, mais lorsque vous créez un tableau d'objets, vous pouvez obtenir la taille du tableau comme ça:
char* chars=new char[100];
printf("%d",*((int*)chars-1));
Le delete[]
la fonction doit déconstruire tous les objets qu'elle contient. pour le faire, le new[]
Le mot clé place le nombre d'éléments derrière tout le tableau.
Le corps du tableau est comme ça:
int count;
ObjectType* data; //This value is returned when using new[]
la façon dont je fais cela est en divisant la taille du tableau par la taille du premier élément
int intarray[100];
printf ("Size of the array %d\n", (sizeof(intarray) / sizeof(intarray[0]));
Il imprime 100