Comment trouver le 'sizeof' (un pointeur pointant vers un tableau)?

309

Tout d'abord, voici un code:

int main() 
{
    int days[] = {1,2,3,4,5};
    int *ptr = days;
    printf("%u\n", sizeof(days));
    printf("%u\n", sizeof(ptr));

    return 0;
}

Existe-t-il un moyen de connaître la taille du tableau qui ptrpointe vers (au lieu de simplement donner sa taille, qui est de quatre octets sur un système 32 bits)?

jkidv
la source
84
J'ai toujours utilisé des parens avec sizeof - bien sûr, cela ressemble à un appel de fonction, mais je pense que c'est plus clair.
Paul Tomblin
20
Pourquoi pas? Avez-vous quelque chose contre les parenthèses superflues? Je pense que ça se lit un peu plus facilement avec eux, moi-même.
David Thornley
6
@ Paul: eh bien .. en supposant que le côté gauche de cet appel est un pointeur vers int, je l'écrirais comme int * ptr = malloc (4 * sizeof * ptr); ce qui pour moi est beaucoup plus clair. Moins de parens à lire et amenant la constante littérale au premier plan, comme en mathématiques.
détendre le
4
@unwind - n'allouez pas un tableau de pointeurs quand vous vouliez dire un tableau d'entiers!
Paul Tomblin
6
Il n'y a pas de "pointeur pointant vers un tableau" ici. Juste un pointeur pointant vers un int.
newacct

Réponses:

269

Non, tu ne peux pas. Le compilateur ne sait pas vers quoi pointe le pointeur. Il existe des astuces, comme terminer le tableau avec une valeur hors bande connue, puis compter la taille jusqu'à cette valeur, mais ce n'est pas utilisé sizeof().

Une autre astuce est celle mentionnée par Zan , qui consiste à cacher la taille quelque part. Par exemple, si vous allouez dynamiquement le tableau, allouez un bloc un entier plus grand que celui dont vous avez besoin, cachez la taille dans le premier entier et retournez ptr+1comme pointeur vers le tableau. Lorsque vous avez besoin de la taille, décrémentez le pointeur et regardez la valeur cachée. N'oubliez pas de libérer tout le bloc à partir du début, et pas seulement le tableau.

Paul Tomblin
la source
12
Je suis désolé d'avoir posté un commentaire si tard mais si le compilateur ne sait pas ce que le pointeur pointe vers comment free sait-il combien de mémoire effacer? Je sais que ces informations sont stockées en interne pour des fonctions comme free to use. Ma question est donc pourquoi le compilateur peut-il le faire aussi?
viki.omega9
11
@ viki.omega9, car free découvre la taille lors de l'exécution. Le compilateur ne peut pas connaître la taille car vous pouvez faire du tableau une taille différente en fonction des facteurs d'exécution (arguments de la ligne de commande, contenu d'un fichier, phase de lune, etc.).
Paul Tomblin
15
Suivi rapide, pourquoi n'y a-t-il pas une fonction qui peut renvoyer la taille comme le fait gratuitement?
viki.omega9
5
Eh bien, si vous pouviez garantir que la fonction n'était appelée qu'avec de la mémoire mallocée et que la bibliothèque suivait la mémoire mallocée de la manière la plus courante (en utilisant un int avant le pointeur renvoyé), alors vous pourriez en écrire un. Mais si le pointeur est sur un tableau statique ou similaire, il échouerait. De même, rien ne garantit que la taille de la mémoire mallocée est accessible à votre programme.
Paul Tomblin
9
@ viki.omega9: Une autre chose à garder à l'esprit est que la taille enregistrée par le système malloc / free peut ne pas être la taille que vous avez demandée. Vous malloc 9 octets et obtenez 16. Malloc 3K octets et obtenez 4K. Ou des situations similaires.
Zan Lynx
85

La réponse est non."

Les programmeurs C stockent la taille du tableau quelque part. Il peut faire partie d'une structure, ou le programmeur peut tricher un peu et malloc()plus de mémoire que demandé afin de stocker une valeur de longueur avant le début du tableau.

Zan Lynx
la source
3
C'est ainsi que les chaînes
pascales
6
et apparemment les cordes pascales sont la raison pour laquelle Excel fonctionne si vite!
Adam Naylor
8
@Adam: C'est rapide. Je l'utilise dans une liste d'implémentation de chaînes. La recherche linéaire est ultra-rapide car elle est: charger la taille, pré-extraire pos + taille, comparer la taille à la taille de la recherche, si strncmp égal, passer à la chaîne suivante, répéter. C'est plus rapide que la recherche binaire jusqu'à environ 500 chaînes.
Zan Lynx
47

Pour les tableaux dynamiques ( malloc ou C ++ nouveau ), vous devez stocker la taille du tableau comme mentionné par d'autres ou peut-être créer une structure de gestionnaire de tableau qui gère l'ajout, la suppression, le comptage, etc. Malheureusement, C ne le fait pas aussi bien que C ++ puisque vous devez essentiellement le construire pour chaque type de tableau différent que vous stockez, ce qui est lourd si vous avez plusieurs types de tableaux que vous devez gérer.

Pour les tableaux statiques, tels que celui de votre exemple, une macro courante est utilisée pour obtenir la taille, mais elle n'est pas recommandée car elle ne vérifie pas si le paramètre est vraiment un tableau statique. Cependant, la macro est utilisée dans du code réel, par exemple dans les en-têtes du noyau Linux, bien qu'elle puisse être légèrement différente de celle ci-dessous:

#if !defined(ARRAY_SIZE)
    #define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0]))
#endif

int main()
{
    int days[] = {1,2,3,4,5};
    int *ptr = days;
    printf("%u\n", ARRAY_SIZE(days));
    printf("%u\n", sizeof(ptr));
    return 0;
}

Vous pouvez google pour des raisons de se méfier de macros comme celle-ci. Faites attention.

Si possible, le stdlib C ++ tel que vector qui est beaucoup plus sûr et plus facile à utiliser.

Ryan
la source
11
ARRAY_SIZE est un paradigme commun utilisé par les programmeurs pratiques partout dans le monde.
Sanjaya R
5
Oui, c'est un paradigme commun. Vous devez toujours l'utiliser avec prudence, car il est facile de l'oublier et de l'utiliser sur un tableau dynamique.
Ryan
2
Oui, bon point, mais la question posée concernait le pointeur, pas le tableau statique.
Paul Tomblin
2
Cette ARRAY_SIZEmacro fonctionne toujours si son argument est un tableau (c'est-à-dire une expression de type tableau). Pour votre soi-disant "tableau dynamique", vous n'obtenez jamais un "tableau" réel (expression de type tableau). (Bien sûr, vous ne pouvez pas, car les types de tableaux incluent leur taille au moment de la compilation.) Vous obtenez simplement un pointeur sur le premier élément. Votre objection "ne vérifie pas si le paramètre est vraiment un tableau statique" n'est pas vraiment valide, car ils sont différents car l'un est un tableau et l'autre ne l'est pas.
newacct
2
Il existe une fonction de modèle flottant qui fait la même chose mais empêchera l'utilisation de pointeurs.
Natalie Adams
18

Il existe une solution propre avec des modèles C ++, sans utiliser sizeof () . La fonction getSize () suivante renvoie la taille de tout tableau statique:

#include <cstddef>

template<typename T, size_t SIZE>
size_t getSize(T (&)[SIZE]) {
    return SIZE;
}

Voici un exemple avec une structure foo_t :

#include <cstddef>

template<typename T, size_t SIZE>
size_t getSize(T (&)[SIZE]) {
    return SIZE;
}

struct foo_t {
    int ball;
};

int main()
{
    foo_t foos3[] = {{1},{2},{3}};
    foo_t foos5[] = {{1},{2},{3},{4},{5}};
    printf("%u\n", getSize(foos3));
    printf("%u\n", getSize(foos5));

    return 0;
}

Production:

3
5
skurton
la source
Je n'ai jamais vu la notation T (&)[SIZE]. Pouvez-vous expliquer ce que cela signifie? Vous pouvez également mentionner constexpr dans ce contexte.
WorldSEnder
2
C'est bien si vous utilisez c ++ et que vous avez en fait une variable de type tableau. Aucun d'eux n'est le cas dans la question: le langage est C, et la chose dont l'OP veut obtenir la taille du tableau est un simple pointeur.
Oguk
ce code conduirait-il à un gonflement du code en recréant le même code pour chaque combinaison taille / type différente ou est-ce optimisé comme par magie par le compilateur?
user2796283
@WorldSEnder: C'est la syntaxe C ++ pour une référence de type tableau (sans nom de variable, juste une taille et un type d'élément).
Peter Cordes
@ user2796283: Cette fonction est entièrement optimisée au moment de la compilation; aucune magie n'est nécessaire; ce n'est pas combiner quoi que ce soit à une seule définition, c'est simplement l'inclure dans une constante de temps de compilation. (Mais dans une version de débogage, oui, vous auriez un tas de fonctions distinctes qui renvoient différentes constantes. La magie de l'éditeur de liens pourrait fusionner celles qui utilisent la même constante. L'appelant ne passe pas SIZEcomme argument, c'est un paramètre de modèle qui a déjà connu par la définition de la fonction.)
Peter Cordes
5

Pour cet exemple spécifique, oui, il y en a, SI vous utilisez des typedefs (voir ci-dessous). Bien sûr, si vous le faites de cette façon, vous pouvez tout aussi bien utiliser SIZEOF_DAYS, car vous savez vers quoi pointe le pointeur.

Si vous avez un pointeur (void *), comme renvoyé par malloc () ou similaire, alors, non, il n'y a aucun moyen de déterminer la structure de données vers laquelle le pointeur pointe et donc, aucun moyen de déterminer sa taille.

#include <stdio.h>

#define NUM_DAYS 5
typedef int days_t[ NUM_DAYS ];
#define SIZEOF_DAYS ( sizeof( days_t ) )

int main() {
    days_t  days;
    days_t *ptr = &days; 

    printf( "SIZEOF_DAYS:  %u\n", SIZEOF_DAYS  );
    printf( "sizeof(days): %u\n", sizeof(days) );
    printf( "sizeof(*ptr): %u\n", sizeof(*ptr) );
    printf( "sizeof(ptr):  %u\n", sizeof(ptr)  );

    return 0;
} 

Production:

SIZEOF_DAYS:  20
sizeof(days): 20
sizeof(*ptr): 20
sizeof(ptr):  4
David
la source
5

Comme toutes les bonnes réponses l'ont indiqué, vous ne pouvez pas obtenir ces informations uniquement à partir de la valeur du pointeur pourri du tableau. Si le pointeur pourri est l'argument reçu par la fonction, alors la taille du tableau d'origine doit être fournie d'une autre manière pour que la fonction connaisse cette taille.

Voici une suggestion différente de ce qui a été fourni jusqu'à présent, qui fonctionnera: passez plutôt un pointeur vers le tableau. Cette suggestion est similaire aux suggestions de style C ++, sauf que C ne prend pas en charge les modèles ou les références:

#define ARRAY_SZ 10

void foo (int (*arr)[ARRAY_SZ]) {
    printf("%u\n", (unsigned)sizeof(*arr)/sizeof(**arr));
}

Mais, cette suggestion est un peu idiote pour votre problème, car la fonction est définie pour connaître exactement la taille du tableau qui est passé (par conséquent, il n'est pas nécessaire d'utiliser sizeof du tout sur le tableau). Cependant, il offre une certaine sécurité de type. Il vous interdira de passer dans un tableau d'une taille indésirable.

int x[20];
int y[10];
foo(&x); /* error */
foo(&y); /* ok */

Si la fonction est censée pouvoir fonctionner sur n'importe quelle taille de tableau, vous devrez fournir la taille de la fonction comme information supplémentaire.

jxh
la source
1
+1 pour "Vous ne pouvez pas obtenir ces informations uniquement à partir de la valeur du pointeur pourri du tableau" et fournir une solution de contournement.
Max
4

Il n'y a pas de solution magique. C n'est pas un langage réflexif. Les objets ne savent pas automatiquement ce qu'ils sont.

Mais vous avez plusieurs choix:

  1. De toute évidence, ajoutez un paramètre
  2. Envelopper l'appel dans une macro et ajouter automatiquement un paramètre
  3. Utilisez un objet plus complexe. Définissez une structure qui contient le tableau dynamique ainsi que la taille du tableau. Ensuite, passez l'adresse de la structure.
DigitalRoss
la source
Les objets savent ce qu'ils sont. Mais si vous pointez sur un sous-objet, il n'y a aucun moyen d'obtenir des informations sur l'objet complet ou un sous
MM
2

Ma solution à ce problème consiste à enregistrer la longueur du tableau dans un tableau struct en tant que méta-informations sur le tableau.

#include <stdio.h>
#include <stdlib.h>

struct Array
{
    int length;

    double *array;
};

typedef struct Array Array;

Array* NewArray(int length)
{
    /* Allocate the memory for the struct Array */
    Array *newArray = (Array*) malloc(sizeof(Array));

    /* Insert only non-negative length's*/
    newArray->length = (length > 0) ? length : 0;

    newArray->array = (double*) malloc(length*sizeof(double));

    return newArray;
}

void SetArray(Array *structure,int length,double* array)
{
    structure->length = length;
    structure->array = array;
}

void PrintArray(Array *structure)
{       
    if(structure->length > 0)
    {
        int i;
        printf("length: %d\n", structure->length);
        for (i = 0; i < structure->length; i++)
            printf("%g\n", structure->array[i]);
    }
    else
        printf("Empty Array. Length 0\n");
}

int main()
{
    int i;
    Array *negativeTest, *days = NewArray(5);

    double moreDays[] = {1,2,3,4,5,6,7,8,9,10};

    for (i = 0; i < days->length; i++)
        days->array[i] = i+1;

    PrintArray(days);

    SetArray(days,10,moreDays);

    PrintArray(days);

    negativeTest = NewArray(-5);

    PrintArray(negativeTest);

    return 0;
}

Mais vous devez vous soucier de définir la bonne longueur du tableau que vous souhaitez stocker, car il n'y a aucun moyen de vérifier cette longueur, comme nos amis l'ont expliqué massivement.


la source
2

Vous pouvez faire quelque chose comme ça:

int days[] = { /*length:*/5, /*values:*/ 1,2,3,4,5 };
int *ptr = days + 1;
printf("array length: %u\n", ptr[-1]);
return 0;
Tᴏᴍᴇʀ Wᴏʟʙᴇʀɢ
la source
1

Non, vous ne pouvez pas utiliser sizeof(ptr)pour trouver la taille du tableau ptrvers laquelle pointe.

Bien que l'allocation de mémoire supplémentaire (plus que la taille du tableau) sera utile si vous souhaitez stocker la longueur dans un espace supplémentaire.

SKD
la source
1
int main() 
{
    int days[] = {1,2,3,4,5};
    int *ptr = days;
    printf("%u\n", sizeof(days));
    printf("%u\n", sizeof(ptr));

    return 0;
}

La taille des jours [] est de 20, ce qui n'est pas un élément * la taille de son type de données. Alors que la taille du pointeur est de 4, peu importe ce qu'il pointe. Parce qu'un pointeur pointe vers un autre élément en stockant son adresse.

Shivangi Chaurasia
la source
1
sizeof (ptr) est la taille du pointeur et sizeof (* ptr) est la taille du pointeur vers lequel
Amitābha
0
 #define array_size 10

 struct {
     int16 size;
     int16 array[array_size];
     int16 property1[(array_size/16)+1]
     int16 property2[(array_size/16)+1]
 } array1 = {array_size, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

 #undef array_size

array_size passe à la variable de taille :

#define array_size 30

struct {
    int16 size;
    int16 array[array_size];
    int16 property1[(array_size/16)+1]
    int16 property2[(array_size/16)+1]
} array2 = {array_size};

#undef array_size

L'utilisation est:

void main() {

    int16 size = array1.size;
    for (int i=0; i!=size; i++) {

        array1.array[i] *= 2;
    }
}
user3065147
la source
0

Dans les chaînes, il y a un '\0'caractère à la fin de sorte que la longueur de la chaîne peut être obtenue en utilisant des fonctions comme strlen. Le problème avec un tableau entier, par exemple, est que vous ne pouvez pas utiliser de valeur comme valeur finale, donc une solution possible consiste à adresser le tableau et à utiliser comme valeur finale le NULLpointeur.

#include <stdio.h>
/* the following function will produce the warning:
 * ‘sizeof’ on array function parameter ‘a’ will
 * return size of ‘int *’ [-Wsizeof-array-argument]
 */
void foo( int a[] )
{
    printf( "%lu\n", sizeof a );
}
/* so we have to implement something else one possible
 * idea is to use the NULL pointer as a control value
 * the same way '\0' is used in strings but this way
 * the pointer passed to a function should address pointers
 * so the actual implementation of an array type will
 * be a pointer to pointer
 */
typedef char * type_t; /* line 18 */
typedef type_t ** array_t;
int main( void )
{
    array_t initialize( int, ... );
    /* initialize an array with four values "foo", "bar", "baz", "foobar"
     * if one wants to use integers rather than strings than in the typedef
     * declaration at line 18 the char * type should be changed with int
     * and in the format used for printing the array values 
     * at line 45 and 51 "%s" should be changed with "%i"
     */
    array_t array = initialize( 4, "foo", "bar", "baz", "foobar" );

    int size( array_t );
    /* print array size */
    printf( "size %i:\n", size( array ));

    void aprint( char *, array_t );
    /* print array values */
    aprint( "%s\n", array ); /* line 45 */

    type_t getval( array_t, int );
    /* print an indexed value */
    int i = 2;
    type_t val = getval( array, i );
    printf( "%i: %s\n", i, val ); /* line 51 */

    void delete( array_t );
    /* free some space */
    delete( array );

    return 0;
}
/* the output of the program should be:
 * size 4:
 * foo
 * bar
 * baz
 * foobar
 * 2: baz
 */
#include <stdarg.h>
#include <stdlib.h>
array_t initialize( int n, ... )
{
    /* here we store the array values */
    type_t *v = (type_t *) malloc( sizeof( type_t ) * n );
    va_list ap;
    va_start( ap, n );
    int j;
    for ( j = 0; j < n; j++ )
        v[j] = va_arg( ap, type_t );
    va_end( ap );
    /* the actual array will hold the addresses of those
     * values plus a NULL pointer
     */
    array_t a = (array_t) malloc( sizeof( type_t *) * ( n + 1 ));
    a[n] = NULL;
    for ( j = 0; j < n; j++ )
        a[j] = v + j;
    return a;
}
int size( array_t a )
{
    int n = 0;
    while ( *a++ != NULL )
        n++;
    return n;
}
void aprint( char *fmt, array_t a )
{
    while ( *a != NULL )
        printf( fmt, **a++ );   
}
type_t getval( array_t a, int i )
{
    return *a[i];
}
void delete( array_t a )
{
    free( *a );
    free( a );
}
baz
la source
Votre code est plein de commentaires, mais je pense que cela faciliterait tout si vous ajoutiez une explication générale de la façon dont cela fonctionne en dehors du code, comme du texte normal. Pouvez-vous modifier votre question et le faire? Je vous remercie!
Fabio dit Réintégrer Monica
La création d'un tableau de pointeurs vers chaque élément afin que vous puissiez le rechercher linéairement NULLest probablement l'alternative la moins efficace imaginable pour simplement stocker un élément séparé sizedirectement. Surtout si vous utilisez tout le temps cette couche supplémentaire d'indirection.
Peter Cordes