¿Cómo encontrar el 'sizeof' (un puntero que apunta a una matriz)?

309

En primer lugar, aquí hay un código:

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

    return 0;
}

¿Hay alguna forma de averiguar el tamaño de la matriz a la que ptrapunta (en lugar de simplemente dar su tamaño, que es de cuatro bytes en un sistema de 32 bits)?

jkidv
fuente
84
Siempre he usado parens con sizeof, seguro que hace que parezca una llamada de función, pero creo que es más claro.
Paul Tomblin el
20
Por qué no? ¿Tienes algo en contra de paréntesis superfluos? Creo que se lee un poco más fácilmente con ellos, yo mismo.
David Thornley el
66
@Paul: bueno ... suponiendo que el lado izquierdo de esa llamada sea un puntero a int, lo escribiría como int * ptr = malloc (4 * sizeof * ptr); lo cual para mí es mucho más claro. Menos parentes para leer, y llevando el constante literal al frente, como en matemáticas.
relajarse
44
@unwind: ¡no asigne una matriz de punteros cuando se refería a una matriz de entradas!
Paul Tomblin el
66
No hay "puntero apuntando a una matriz" aquí. Solo un puntero apuntando a un int.
newacct

Respuestas:

269

No puedes. El compilador no sabe a qué apunta el puntero. Hay trucos, como terminar la matriz con un valor fuera de banda conocido y luego contar el tamaño hasta ese valor, pero eso no se usa sizeof().

Otro truco es el mencionado por Zan , que consiste en esconder el tamaño en algún lugar. Por ejemplo, si está asignando dinámicamente la matriz, asigne un bloque uno más grande que el que necesita, guarde el tamaño en la primera int y regrese ptr+1como el puntero a la matriz. Cuando necesite el tamaño, disminuya el puntero y mire el valor oculto. Solo recuerde liberar todo el bloque desde el principio, y no solo la matriz.

Paul Tomblin
fuente
12
Lamento que haya publicado un comentario tan tarde, pero si el compilador no sabe a qué apunta el puntero, ¿cómo sabe Free cuánta memoria borrar? Sé que esta información se almacena internamente para funciones como de uso gratuito. Entonces mi pregunta es ¿por qué 'el compilador también puede hacerlo?
viki.omega9
11
@ viki.omega9, porque gratis descubre el tamaño en tiempo de ejecución. El compilador no puede conocer el tamaño porque podría hacer que la matriz tenga un tamaño diferente dependiendo de los factores de tiempo de ejecución (argumentos de la línea de comandos, contenido de un archivo, fase de la luna, etc.).
Paul Tomblin
15
Seguimiento rápido, ¿por qué no hay una función que pueda devolver el tamaño como lo hace gratis?
viki.omega9
55
Bueno, si pudieras garantizar que la función solo se llamara con memoria mal colocada y la biblioteca rastrea la memoria mallocedada de la manera que más he visto hacer (usando un int antes del puntero devuelto), entonces podrías escribir una. Pero si el puntero apunta a una matriz estática o similar, fallaría. Del mismo modo, no hay garantía de que el tamaño de la memoria mal asignada sea accesible para su programa.
Paul Tomblin
99
@ viki.omega9: Otra cosa a tener en cuenta es que el tamaño registrado por el sistema malloc / free puede no ser el tamaño que solicitó. Malloc 9 bytes y obtener 16. Malloc 3K bytes y obtener 4K. O situaciones similares.
Zan Lynx
85

La respuesta es no."

Lo que hacen los programadores de C es almacenar el tamaño de la matriz en algún lugar. Puede ser parte de una estructura, o el programador puede engañar un poco y malloc()más memoria de la solicitada para almacenar un valor de longitud antes del inicio de la matriz.

Zan Lynx
fuente
3
Así es como se implementan las cadenas
pascales
66
¡y aparentemente las cuerdas pascales son la razón por la que Excel funciona tan rápido!
Adam Naylor
8
@ Adam: es rápido. Lo uso en una lista de cadenas de implementación mía. Es súper rápido para la búsqueda lineal porque es: tamaño de carga, captación previa pos + tamaño, comparación de tamaño con tamaño de búsqueda, si strncmp es igual, pasar a la siguiente cadena, repetir. Es más rápido que la búsqueda binaria de hasta aproximadamente 500 cadenas.
Zan Lynx
47

Para las matrices dinámicas ( malloc o C ++ nuevo ) necesita almacenar el tamaño de la matriz como lo mencionan otros o tal vez construir una estructura de administrador de matriz que maneje agregar, eliminar, contar, etc. Desafortunadamente, C no hace esto tan bien como C ++ ya que básicamente tiene que compilarlo para cada tipo de matriz diferente que está almacenando, lo cual es engorroso si tiene múltiples tipos de matrices que necesita administrar.

Para las matrices estáticas, como la de su ejemplo, hay una macro común utilizada para obtener el tamaño, pero no se recomienda ya que no comprueba si el parámetro es realmente una matriz estática. Sin embargo, la macro se usa en código real, por ejemplo, en los encabezados del kernel de Linux, aunque puede ser ligeramente diferente de la siguiente:

#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;
}

Puede buscar en Google por razones para desconfiar de macros como esta. Ten cuidado.

Si es posible, el C ++ stdlib como vector, que es mucho más seguro y fácil de usar.

Ryan
fuente
11
ARRAY_SIZE es un paradigma común utilizado por programadores prácticos en todas partes.
Sanjaya R
55
Sí, es un paradigma común. Sin embargo, aún debe usarlo con precaución, ya que es fácil de olvidar y usar en una matriz dinámica.
Ryan
2
Sí, buen punto, pero la pregunta que se hizo fue sobre el puntero, no sobre la matriz estática.
Paul Tomblin el
2
Esa ARRAY_SIZEmacro siempre funciona si su argumento es una matriz (es decir, expresión del tipo de matriz). Para su llamada "matriz dinámica", nunca obtiene una "matriz" real (expresión del tipo de matriz). (Por supuesto, no puede hacerlo, ya que los tipos de matriz incluyen su tamaño en tiempo de compilación). Simplemente obtiene un puntero al primer elemento. Su objeción "no verifica si el parámetro es realmente una matriz estática" no es realmente válida, ya que son diferentes ya que uno es un conjunto y el otro no.
newacct
2
Hay una función de plantilla flotando que hace lo mismo pero evitará el uso de punteros.
Natalie Adams
18

Hay una solución limpia con plantillas C ++, sin usar sizeof () . La siguiente función getSize () devuelve el tamaño de cualquier matriz estática:

#include <cstddef>

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

Aquí hay un ejemplo con una estructura 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;
}

Salida:

3
5
skurton
fuente
Nunca he visto la notación T (&)[SIZE]. ¿Puedes explicar qué significa esto? También podría mencionar constexpr en este contexto.
WorldSEnder
2
Eso es bueno si usas c ++ y realmente tienes una variable de un tipo de matriz. Ninguno de ellos es el caso en la pregunta: el lenguaje es C, y lo que el OP quiere obtener del tamaño de la matriz es un puntero simple.
Oguk
¿conduciría este código a una acumulación de código al recrear el mismo código para cada combinación de tamaño / tipo diferente o el compilador lo optimiza mágicamente?
user2796283
@WorldSEnder: esa es la sintaxis de C ++ para una referencia de tipo de matriz (sin nombre de variable, solo un tamaño y tipo de elemento).
Peter Cordes
@ user2796283: esta función se optimiza por completo en tiempo de compilación; no se necesita magia; no combina nada en una sola definición, simplemente lo alinea a una constante de tiempo de compilación. (Pero en una construcción de depuración, sí, tendrías un montón de funciones separadas que devuelven diferentes constantes. La magia del enlazador podría fusionar las que usan la misma constante. La persona que llama no pasa SIZEcomo un argumento, es un parámetro de plantilla que tiene ser conocido por la definición de la función.)
Peter Cordes
5

Para este ejemplo específico, sí, existe, SI usa typedefs (ver más abajo). Por supuesto, si lo hace de esta manera, puede usar SIZEOF_DAYS, ya que sabe a qué apunta el puntero.

Si tiene un puntero (void *), como lo devuelve malloc () o similar, entonces no, no hay forma de determinar a qué estructura de datos apunta el puntero y, por lo tanto, no hay forma de determinar su tamaño.

#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;
} 

Salida:

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

Como han indicado todas las respuestas correctas, no puede obtener esta información solo del valor del puntero decaído de la matriz. Si el puntero decaído es el argumento recibido por la función, entonces el tamaño de la matriz de origen debe proporcionarse de alguna otra manera para que la función conozca ese tamaño.

Aquí hay una sugerencia diferente de la que se ha proporcionado hasta ahora, que funcionará: en su lugar, pase un puntero a la matriz. Esta sugerencia es similar a las sugerencias de estilo C ++, excepto que C no admite plantillas o referencias:

#define ARRAY_SZ 10

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

Pero, esta sugerencia es un poco tonta para su problema, ya que la función se define para conocer exactamente el tamaño de la matriz que se pasa (por lo tanto, hay poca necesidad de usar sizeof en la matriz). Sin embargo, lo que sí hace es ofrecer algún tipo de seguridad. Le prohibirá pasar en una matriz de un tamaño no deseado.

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

Si se supone que la función puede operar en cualquier tamaño de matriz, deberá proporcionar el tamaño de la función como información adicional.

jxh
fuente
1
+1 para "No puede obtener esta información solo del valor del puntero decaído de la matriz" y proporcionar una solución alternativa.
Max
4

No hay una solución mágica. C no es un lenguaje reflexivo. Los objetos no saben automáticamente qué son.

Pero tienes muchas opciones:

  1. Obviamente, agregue un parámetro
  2. Envuelva la llamada en una macro y agregue automáticamente un parámetro
  3. Usa un objeto más complejo. Defina una estructura que contenga la matriz dinámica y también el tamaño de la matriz. Luego, pase la dirección de la estructura.
DigitalRoss
fuente
Los objetos saben lo que son. Pero si señala un subobjeto, no hay forma de obtener información sobre el objeto completo o un subobjeto más grande
MM
2

Mi solución a este problema es guardar la longitud de la matriz en una estructura Array como una metainformación sobre la matriz.

#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;
}

Pero debe preocuparse por establecer la longitud correcta de la matriz que desea almacenar, porque no hay forma de verificar esta longitud, como explicaron masivamente nuestros amigos.


fuente
2

Puedes hacer algo como esto:

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ᴏʟʙᴇʀɢ
fuente
1

No, no puede usar sizeof(ptr)para encontrar el tamaño de la matriz a la ptrque apunta.

Aunque asignar memoria adicional (más del tamaño de la matriz) será útil si desea almacenar la longitud en espacio adicional.

SKD
fuente
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;
}

Tamaño de días [] es 20, que no es de elementos * tamaño de su tipo de datos. Si bien el tamaño del puntero es 4, no importa a qué apunte. Porque un puntero apunta a otro elemento almacenando su dirección.

Shivangi Chaurasia
fuente
1
sizeof (ptr) es el tamaño del puntero y sizeof (* ptr) es el tamaño del puntero al cual
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 pasa a la variable de tamaño :

#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

El uso es:

void main() {

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

        array1.array[i] *= 2;
    }
}
usuario3065147
fuente
0

En las cadenas hay un '\0'carácter al final, por lo que la longitud de la cadena se puede obtener utilizando funciones como strlen. El problema con una matriz de enteros, por ejemplo, es que no puede usar ningún valor como valor final, por lo que una posible solución es abordar la matriz y usar como NULLpuntero el valor final .

#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
fuente
Su código está lleno de comentarios, pero creo que facilitaría todo si agregara una explicación general de cómo funciona esto fuera del código, como texto normal. ¿Puedes editar tu pregunta y hacerlo? ¡Gracias!
Fabio dice Restablecer a Monica el
Crear una matriz de punteros para cada elemento para que pueda buscarlo linealmente NULLes probablemente la alternativa menos eficiente imaginable para almacenar sizedirectamente un elemento separado . Especialmente si realmente usa esta capa adicional de indirección todo el tiempo.
Peter Cordes