Como encontrar o 'sizeof' (um ponteiro apontando para uma matriz)?

309

Primeiro, aqui está um 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;
}

Existe uma maneira de descobrir o tamanho da matriz que ptrestá apontando (em vez de apenas fornecer seu tamanho, que é de quatro bytes em um sistema de 32 bits)?

jkidv
fonte
84
Eu sempre usei parens com sizeof - com certeza faz parecer uma chamada de função, mas acho que é mais claro.
21139 Paul Tomblin
20
Por que não? Você tem algo contra parênteses supérfluos? Eu acho que lê um pouco mais facilmente com eles, eu mesmo.
David Thornley
6
@ Paul: bem ... supondo que o lado esquerdo dessa chamada seja um ponteiro para int, eu escreveria como int * ptr = malloc (4 * sizeof * ptr); o que para mim é muito mais claro. Menos parênteses para ler e trazer o literal literal para a frente, como na matemática.
29333 desenrolar
4
@unwind - não aloque uma matriz de ponteiros quando você quis dizer uma matriz de entradas!
Paul Tomblin
6
Não há "ponteiro apontando para uma matriz" aqui. Apenas um ponteiro apontando para um int.
newacct

Respostas:

269

Não, você não pode. O compilador não sabe para onde o ponteiro está apontando. Existem truques, como finalizar a matriz com um valor fora da banda conhecido e contar o tamanho até esse valor, mas isso não está sendo usado sizeof().

Outro truque é o mencionado por Zan , que é esconder o tamanho em algum lugar. Por exemplo, se você estiver alocando dinamicamente a matriz, aloque um bloco um int maior que o necessário, armazene o tamanho no primeiro int e retorne ptr+1como ponteiro para o array. Quando você precisar do tamanho, diminua o ponteiro e espreite o valor escondido. Lembre-se de liberar todo o bloco começando do início, e não apenas o array.

Paul Tomblin
fonte
12
Sinto muito por postar um comentário tão tarde, mas se o compilador não souber o que o ponteiro está apontando, como o free saberá quanta memória limpar? Sei que essas informações são armazenadas internamente para funções como o uso gratuito. Então, minha pergunta é por que o 'compilador também pode fazer isso?
viki.omega9 03/03
11
@ viki.omega9, porque o free descobre o tamanho no tempo de execução. O compilador não pode saber o tamanho, porque você pode tornar a matriz com um tamanho diferente, dependendo dos fatores de tempo de execução (argumentos da linha de comando, conteúdo de um arquivo, fase da lua, etc.).
Paul Tomblin
15
Acompanhamento rápido, por que não existe uma função que possa retornar o tamanho da maneira que é gratuita?
viki.omega9 03/03
5
Bem, se você pudesse garantir que a função fosse chamada apenas com memória em malloced e a biblioteca rastreie a memória em malloced da maneira que eu já vi (usando um int antes do ponteiro retornado), você poderá escrever um. Mas se o ponteiro for para uma matriz estática ou algo semelhante, ele falhará. Da mesma forma, não há garantia de que o tamanho da memória em malloced esteja acessível ao seu programa.
Paul Tomblin 03/03
9
@ viki.omega9: Outra coisa a ter em mente é que o tamanho registrado pelo sistema malloc / free pode não ser o tamanho solicitado. Você malloc 9 bytes e obtém 16. Malloc 3K bytes e obtém 4K. Ou situações semelhantes.
Zan Lynx
85

A resposta é não."

O que os programadores C fazem é armazenar o tamanho da matriz em algum lugar. Pode fazer parte de uma estrutura ou o programador pode enganar um pouco malloc()mais de memória do que o solicitado para armazenar um valor de comprimento antes do início da matriz.

Zan Lynx
fonte
3
Isso é como cordas pascal são implementadas
dsm
6
e, aparentemente, cordas pascal são o motivo pelo qual o excel roda tão rápido!
Adam Naylor
8
@ Adam: É rápido. Eu o uso em uma lista de implementações de strings minhas. É super rápida a pesquisa linear porque é: tamanho da carga, pré-busca pos + tamanho, compare o tamanho com o tamanho da pesquisa, se strncmp igual, move para a próxima seqüência, repita. É mais rápido que a pesquisa binária, com até 500 strings.
Zan Lynx
47

Para matrizes dinâmicas ( malloc ou C ++ novo ), é necessário armazenar o tamanho da matriz como mencionado por outros ou talvez criar uma estrutura de gerenciador de matriz que lide com adicionar, remover, contar etc. Infelizmente, C não faz isso tão bem quanto C ++, já que você basicamente precisa criá-lo para cada tipo de matriz diferente que você está armazenando, o que é complicado se você tiver vários tipos de matrizes que precisa gerenciar.

Para matrizes estáticas, como a do seu exemplo, existe uma macro comum usada para obter o tamanho, mas isso não é recomendado , pois não verifica se o parâmetro é realmente uma matriz estática. A macro é usada no código real, por exemplo, nos cabeçalhos do kernel do Linux, embora possa ser um pouco diferente da abaixo:

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

Você pode pesquisar no Google por motivos de desconfiança em relação a macros como essa. Seja cuidadoso.

Se possível, o stdlib do C ++, como vetor, é muito mais seguro e fácil de usar.

Ryan
fonte
11
ARRAY_SIZE é um paradigma comum usado por programadores práticos em todos os lugares.
Sanjaya R
5
Sim, é um paradigma comum. Você ainda precisa usá-lo com cautela, pois é fácil esquecer e usá-lo em uma matriz dinâmica.
Ryan
2
Sim, bom ponto, mas a pergunta feita era sobre o ponteiro, não o estático.
Paul Tomblin
2
Essa ARRAY_SIZEmacro sempre funciona se seu argumento for uma matriz (ou seja, expressão do tipo de matriz). Para a chamada "matriz dinâmica", você nunca obtém uma "matriz" real (expressão do tipo de matriz). (Obviamente, você não pode, pois os tipos de matriz incluem seu tamanho em tempo de compilação.) Você apenas obtém um ponteiro para o primeiro elemento. Sua objeção "não verifica se o parâmetro é realmente uma matriz estática" não é realmente válida, pois são diferentes porque uma é uma matriz e a outra não.
New281
2
Existe uma função de modelo flutuando que faz a mesma coisa, mas impede o uso de ponteiros.
Natalie Adams
18

Existe uma solução limpa com modelos C ++, sem usar sizeof () . A seguinte função getSize () retorna o tamanho de qualquer matriz estática:

#include <cstddef>

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

Aqui está um exemplo com uma estrutura 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;
}

Resultado:

3
5
Skurton
fonte
Eu nunca vi a notação T (&)[SIZE]. Você pode explicar o que isso significa? Você também pode mencionar constexpr neste contexto.
WorldSEnder
2
Isso é bom se você usa c ++ e você realmente tem uma variável de um tipo de matriz. Nenhum deles é o caso da questão: a linguagem é C, e a coisa que o OP deseja obter o tamanho da matriz é um simples ponteiro.
Oguk
esse código levaria ao inchaço do código recriando o mesmo código para cada combinação de tamanho / tipo diferente ou isso é magicamente otimizado para não existir pelo compilador?
user2796283
@ WorldSEnder: Essa é a sintaxe C ++ para uma referência do tipo de matriz (sem nome de variável, apenas tamanho e tipo de elemento).
Peter Cordes
@ user2796283: Esta função é totalmente otimizada em tempo de compilação; nenhuma mágica é necessária; não está combinando nada com uma única definição, é simplesmente incorporando-a a uma constante em tempo de compilação. (Mas em uma compilação de depuração, sim, você teria várias funções separadas que retornam constantes diferentes. A mágica do vinculador pode mesclar aquelas que usam a mesma constante. O chamador não passa SIZEcomo um argumento, é um parâmetro de modelo que possui para já ser conhecida pela definição de função).
Peter Cordes
5

Para este exemplo específico, sim, existe, se você usar typedefs (veja abaixo). Obviamente, se você fizer dessa maneira, poderá usar SIZEOF_DAYS, pois sabe para onde o ponteiro está apontando.

Se você tiver um ponteiro (void *), retornado por malloc () ou algo semelhante, não, não há como determinar para qual estrutura de dados o ponteiro está apontando e, portanto, não há como determinar seu tamanho.

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

Resultado:

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

Como todas as respostas corretas declararam, você não pode obter essas informações apenas com o valor do ponteiro deteriorado da matriz. Se o ponteiro deteriorado é o argumento recebido pela função, o tamanho da matriz de origem deve ser fornecido de outra maneira para que a função conheça esse tamanho.

Aqui está uma sugestão diferente do que foi fornecido até agora, que funcionará: passe um ponteiro para a matriz. Essa sugestão é semelhante às sugestões de estilo C ++, exceto que C não suporta modelos ou referências:

#define ARRAY_SZ 10

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

Mas, essa sugestão é meio boba para o seu problema, já que a função é definida para saber exatamente o tamanho da matriz que é passada (portanto, há pouca necessidade de usar sizeof na matriz). O que ele faz, no entanto, é oferecer algum tipo de segurança. Isso o proibirá de transmitir uma matriz de tamanho indesejado.

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

Se a função deve poder operar em qualquer tamanho de matriz, você precisará fornecer o tamanho da função como informações adicionais.

jxh
fonte
1
+1 para "Você não pode obter essas informações apenas do valor do ponteiro deteriorado da matriz" e fornecendo uma solução alternativa.
Max
4

Não há solução mágica. C não é uma linguagem reflexiva. Os objetos não sabem automaticamente o que são.

Mas você tem muitas opções:

  1. Obviamente, adicione um parâmetro
  2. Embrulhe a chamada em uma macro e adicione automaticamente um parâmetro
  3. Use um objeto mais complexo. Defina uma estrutura que contém a matriz dinâmica e também o tamanho da matriz. Em seguida, passe o endereço da estrutura.
DigitalRoss
fonte
Objetos sabem o que são. Mas se você apontar para um subobjeto, não há como obter informações sobre o objeto completo ou sobre um subobjeto maior.
MM
2

Minha solução para esse problema é salvar o comprimento da matriz em uma matriz de estrutura como uma meta-informação sobre a 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;
}

Mas você precisa se preocupar em definir o tamanho certo da matriz que deseja armazenar, porque não há como verificar esse tamanho, como explicaram massivamente nossos amigos.


fonte
2

Você pode fazer algo assim:

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

Não, você não pode sizeof(ptr)encontrar o tamanho da matriz que ptrestá apontando.

Embora alocar memória extra (mais do que o tamanho da matriz) seja útil se você deseja armazenar o comprimento em espaço extra.

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

Tamanho dos dias [] é 20, que não possui elementos * tamanho do seu tipo de dados. Enquanto o tamanho do ponteiro é 4, não importa para o que ele está apontando. Porque um ponteiro aponta para outro elemento armazenando seu endereço.

Shivangi Chaurasia
fonte
1
sizeof (ptr) é o tamanho do ponteiro e sizeof (* ptr) é o tamanho do ponteiro para o qual
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 está passando para a variável size :

#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

O uso é:

void main() {

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

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

Nas strings existe um '\0'caractere no final, então o comprimento da string pode ser obtido usando funções como strlen. O problema com uma matriz inteira, por exemplo, é que você não pode usar nenhum valor como valor final; portanto, uma solução possível é endereçar a matriz e usar como NULLponteiro o 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
fonte
Seu código está cheio de comentários, mas acho que facilitaria tudo se você adicionasse uma explicação geral de como isso funciona fora do código, como texto normal. Você pode editar sua pergunta e fazê-lo? Obrigado!
Fabio diz Reinstate Monica
Criar uma matriz de ponteiros para cada elemento para que você possa pesquisá-lo linearmente NULLé provavelmente a alternativa menos eficiente que se pode imaginar para armazenar sizediretamente um item separado . Especialmente se você realmente usa essa camada extra de indireção o tempo todo.
Peter Cordes