2009-04-06 22 views
13

estoy usando la siguiente macro para calcular el tamaño de un array:tamaño Cálculo de una matriz

#define G_N_ELEMENTS(arr) ((sizeof(arr))/(sizeof(arr[0]))) 

Sin embargo veo una discrepancia en el valor calculado por él cuando evalúo el tamaño de una matriz en una función (valor incorrecto calculado) en lugar de llamar a la función (se calcula el valor correcto). Código + salida a continuación. Cualquier pensamiento, sugerencia, consejos, etc. Bienvenido.

DP

#include <stdio.h> 

#define G_N_ELEMENTS(arr) ((sizeof(arr))/(sizeof(arr[0]))) 

void foo(int * arr) // Also tried foo(int arr[]), foo(int * & arr) 
        // - neither of which worked 
{ 
    printf("arr : %x\n", arr); 
    printf ("sizeof arr: %d\n", G_N_ELEMENTS(arr)); 
} 

int main() 
{ 
    int arr[] = {1, 2, 3, 4}; 

    printf("arr : %x\n", arr); 
    printf ("sizeof arr: %d\n", G_N_ELEMENTS(arr)); 

    foo(arr); 
} 

Salida:

arr : bffffa40 
sizeof arr: 4 
arr : bffffa40 
sizeof arr: 1 
+0

Llamar al parámetro foo con el mismo nombre hace que esta intuición sea intuitiva para usted. Considere darle otro nombre para simular lo que está mirando el compilador. – ojblass

Respuesta

12

Esto no está funcionando porque sizeof se calcula en tiempo de compilación. La función no tiene información sobre el tamaño de su parámetro (solo sabe que apunta a una dirección de memoria).

Considere utilizar un vector STL en su lugar, o pasar en tamaños de matriz como parámetros a las funciones.

1

Solo debe llamar a sizeof en la matriz. Cuando llama a sizeof en el tipo de puntero, el tamaño siempre será 4 (u 8, o lo que sea que haga su sistema).

La notación húngara de MSFT puede ser fea, pero si la usas, sabes que no debes llamar a tu macro en nada que empiece con una 'p'.

También verifique la definición de la macro ARRAYSIZE() en WinNT.h. Si está usando C++ puede hacer cosas extrañas con las plantillas para obtener el tiempo de compilación si lo hace de esa manera.

+0

Puede llamar a sizeof() en el puntero, pero el tamaño que obtenga será el tamaño del puntero, no el tamaño de la matriz. –

+0

Buen punto. Déjame arreglar eso. –

2

Si cambia el foo Funciton un poco se podría hacer sentir un poco más cómodo:

void foo(int * pointertofoo) 
{    
    printf("pointertofoo : %x\n", pointertofoo); 
    printf ("sizeof pointertofoo: %d\n", G_N_ELEMENTS(pointertofoo)); 
} 

Eso es lo que el compilador ver algo que es completamente un contexto diferente a la función.

27

Eso es debido a que el tamaño de un int * es del tamaño de un int puntero (4 u 8 bytes en plataformas modernas que utilizo pero depende enteramente de la plataforma). El sizeof se calcula en tiempo de compilación, no en tiempo de ejecución, por lo que incluso sizeof (arr[]) no ayudará porque puede llamar a la función foo() en tiempo de ejecución con muchas matrices de diferentes tamaños.

El tamaño de una matriz int es el tamaño de una matriz int.

Este es uno de los bits difíciles en C/C++; el uso de matrices y punteros no siempre es idéntico. Las matrices, en muchas circunstancias, decaerán a un puntero al primer elemento de esa matriz.

Hay al menos dos soluciones, compatible con C y C++:

  • pase la longitud en con la matriz (no que útil si la finalidad de la función es trabajar en realidad fuera de la matriz tamaño).
  • pase un valor centinela que marque el final de los datos, por ejemplo, {1,2,3,4,-1}.
4

Tenga en cuenta que incluso si se trata de decir que el compilador C, el tamaño de la matriz en la función, no se dio por aludido (mi DIM es equivalente a su G_N_ELEMENTS):

#include <stdio.h> 

#define DIM(x) (sizeof(x)/sizeof(*(x))) 

static void function(int array1[], int array2[4]) 
{ 
    printf("array1: size = %u\n", (unsigned)DIM(array1)); 
    printf("array2: size = %u\n", (unsigned)DIM(array2)); 
} 

int main(void) 
{ 
    int a1[40]; 
    int a2[4]; 
    function(a1, a2); 
    return(0); 
} 

Imprime:

array1: size = 1 
array2: size = 1 

Si desea saber qué tan grande es la matriz dentro de una función, pase el tamaño a la función. O bien, en C++, use cosas como STL vector<int>.

+0

+1. El hecho de que C++ al menos (y tal vez C, no estoy seguro) no respeta el tamaño dado, o incluso dar una advertencia/error, es bastante reprobable en mi humilde opinión. –

+0

Sí, me preguntaba acerca de sizeof (parámetros de tipo de matriz completa) yo mismo. – aib

2
foo(int * arr) //Also tried foo(int arr[]), foo(int * & arr) 
{    // - neither of which worked 
    printf("arr : %x\n", arr); 
    printf ("sizeof arr: %d\n", G_N_ELEMENTS(arr)); 
} 

sizeof (arr) es sizeof (int *), es decir. 4

A menos que tenga una muy buena razón para escribir un código como este, NO HAGA. Estamos en el siglo XXI ahora, use std :: vector en su lugar.

Para obtener más información, consulte el C++ FAQ: http://www.parashift.com/c++-faq-lite/containers.html

Recuerde: "Las matrices son malos"

+0

¿No debería ser eso: "estamos en el siglo XXI ahora, ya no es necesario usar C++" ;-) – JesperE

+0

¿Tiene algo más adecuado ...? :-) –

10

En C++, puede definir G_N_ELEMENTS así:

template<typename T, size_t N> 
size_t G_N_ELEMENTS(T (&array)[N]) 
{ 
    return N; 
} 

Si desea use el tamaño de la matriz en tiempo de compilación, así es como:

// ArraySize 
template<typename T> 
struct ArraySize; 

template<typename T, size_t N> 
struct ArraySize<T[N]> 
{ 
    enum{ value = N }; 
}; 

Gracias j_random_hacker para corregir mis errores y proporcionar información adicional.

+0

Esta es una gran definición alternativa que no se compilará si se pasa un puntero. +1 –

+0

Aunque haré del parámetro una referencia constante. –

+1

Aunque es posible que necesite el valor en tiempo de compilación, en cuyo caso una mejor alternativa es: template struct G_N_ELEMENTS; template struct G_N_ELEMENTS {static size_t value = N; }; –

4

Editar: C++ 11 se introdujo desde esta respuesta fue escrito, e incluye funciones para hacer exactamente lo que muestro a continuación: std::begin y std::end. Las versiones de Const std::cbegin y std::cend también van a una versión futura del estándar (C++ 14?) Y pueden estar ya en su compilador. Ni siquiera considere usar mis funciones a continuación si tiene acceso a las funciones estándar.


Me gustaría construir un poco en Benoît's answer.

En lugar de pasar solo la dirección de inicio de la matriz como un puntero, o un puntero más el tamaño sugerido por otros, tome como referencia la biblioteca estándar y pase dos punteros al principio y al final de la matriz. Esto no solo hace que tu código se parezca más al C++ moderno, ¡sino que puedes utilizar cualquiera de los algoritmos de biblioteca estándar en tu matriz!

template<typename T, int N> 
T * BEGIN(T (& array)[N]) 
{ 
    return &array[0]; 
} 

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

template<typename T, int N> 
const T * BEGIN_CONST(const T (& array)[N]) 
{ 
    return &array[0]; 
} 

template<typename T, int N> 
const T * END_CONST(const T (& array)[N]) 
{ 
    return &array[N]; 
} 

void 
foo(int * begin, int * end) 
{ 
    printf("arr : %x\n", begin); 
    printf ("sizeof arr: %d\n", end - begin); 
} 

int 
main() 
{ 
    int arr[] = {1, 2, 3, 4}; 

    printf("arr : %x\n", arr); 
    printf ("sizeof arr: %d\n", END(arr) - BEGIN(arr)); 

    foo(BEGIN(arr), END(arr)); 
} 

Aquí hay una definición alternativa para BEGIN y END, si las plantillas no funcionan.

#define BEGIN(array) array 
#define END(array) (array + sizeof(array)/sizeof(array[0])) 

Actualización: El código anterior con las plantillas trabaja en MS VC++ 2005 y GCC 3.4.6, como debe ser. Necesito obtener un nuevo compilador.

También estoy reconsiderando la convención de nomenclatura utilizada aquí: las funciones de plantilla enmascaradas como macros simplemente se sienten mal. Estoy seguro de que usaré esto en mi propio código pronto, y creo que usaré ArrayBegin, ArrayEnd, ArrayConstBegin y ArrayConstEnd.

+0

+1, buena idea, pero cambie "T [N] & array" (que no es un tipo válido de C++, y no se compilará) a "T (& array) [N]". –

+0

@j_random_hacker: ¿Puedes explicar por qué T [N] & no es un tipo válido de C++? –

+0

@Benoit: Son solo las reglas de la sintaxis C/C++; requieren límites de matriz * después de * el nombre del identificador (que está implícito aquí). De la misma manera, para declarar una matriz de 10 ints llamada foo, debe escribir "int foo [10];" no "int [10] foo;" - aunque este último parece razonable, no se analizará. –

0

Ahora que tenemos constexpr en C++ 11, la versión tipo seguro (no macro) también se puede usar en una expresión constante.

template<typename T, std::size_t size> 
constexpr std::size_t array_size(T const (&)[size]) { return size; } 

Esto dejará de recopilar donde no funciona correctamente, a diferencia de su solución de macro (no va a funcionar sobre los punteros por accidente). Se puede utilizar cuando se requiere una constante de tiempo de compilación:

int new_array[array_size(some_other_array)]; 

Dicho esto, usted es mejor usar std::array para este si es posible. No prestes atención a las personas que dicen usar std::vector porque es mejor. std::vector es una estructura de datos diferente con diferentes puntos fuertes. std::array no tiene sobrecarga en comparación con una matriz de estilo C, pero a diferencia de la matriz de estilo C, no decaerá a un puntero a la menor provocación. std::vector, por otro lado, requiere que todos los accesos sean accesos indirectos (pasan por un puntero) y su uso requiere una asignación dinámica. Una cosa a tener en cuenta si estás acostumbrado al uso de matrices de tipo C es estar seguro de pasar std::array a una función como esta:

void f(std::array<int, 100> const & array); 

Si usted no pasa por referencia, se copian los datos. Esto sigue el comportamiento de la mayoría de los tipos bien diseñados, pero es diferente de las matrices de estilo C cuando se pasa a una función (es más parecido al comportamiento de una matriz estilo C dentro de una estructura).

Cuestiones relacionadas