Làm cách nào để tìm 'sizeof' (một con trỏ trỏ đến một mảng)?


309

Trước hết, đây là một số mã:

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

    return 0;
}

Có cách nào để tìm ra kích thước của mảng ptrđang trỏ tới (thay vì chỉ đưa ra kích thước của nó, đó là bốn byte trên hệ thống 32 bit)?


84
Tôi đã luôn sử dụng parens với sizeof - chắc chắn rằng nó làm cho nó giống như một cuộc gọi chức năng, nhưng tôi nghĩ nó rõ ràng hơn.
Paul Tomblin

20
Tại sao không? Bạn có điều gì chống lại dấu ngoặc đơn không cần thiết? Tôi nghĩ rằng nó đọc dễ dàng hơn một chút với họ, bản thân tôi.
David Thornley

6
@Paul: tốt .. giả sử phía bên trái của cuộc gọi đó là một con trỏ tới int, tôi sẽ viết nó là int * ptr = malloc (4 * sizeof * ptr); mà với tôi là rõ ràng hơn nhiều. Ít parens để đọc, và đưa các hằng số theo nghĩa đen lên phía trước, giống như trong toán học.
thư giãn

4
@unwind - không phân bổ một mảng các con trỏ khi bạn có nghĩa là một mảng ints!
Paul Tomblin

6
Không có "con trỏ trỏ đến một mảng" ở đây. Chỉ cần một con trỏ trỏ đến một int.
newacct

Câu trả lời:


269

Không, bạn không thể. Trình biên dịch không biết con trỏ trỏ tới cái gì. Có các thủ thuật, như kết thúc mảng với giá trị ngoài băng đã biết và sau đó đếm kích thước cho đến giá trị đó, nhưng điều đó không sử dụng sizeof().

Một mẹo khác là Zan được đề cập , đó là cất kích thước ở đâu đó. Ví dụ: nếu bạn tự động phân bổ mảng, hãy phân bổ một khối một int lớn hơn khối bạn cần, bỏ kích thước trong int đầu tiên và trả về ptr+1làm con trỏ cho mảng. Khi bạn cần kích thước, giảm con trỏ và nhìn vào giá trị được đặt. Chỉ cần nhớ giải phóng toàn bộ khối bắt đầu từ đầu, và không chỉ mảng.


12
Tôi xin lỗi vì đã đăng một bình luận quá muộn nhưng nếu trình biên dịch không biết con trỏ đang trỏ đến cái gì thì làm thế nào để biết có bao nhiêu bộ nhớ để xóa? Tôi biết rằng thông tin này được lưu trữ nội bộ cho các chức năng như miễn phí sử dụng. Vì vậy, câu hỏi của tôi là tại sao 'trình biên dịch có thể làm như vậy quá?
viki.omega9

11
@ viki.omega9, vì miễn phí phát hiện ra kích thước khi chạy. Trình biên dịch không thể biết kích thước vì bạn có thể làm cho mảng có kích thước khác tùy thuộc vào các yếu tố thời gian chạy (đối số dòng lệnh, nội dung của tệp, pha của mặt trăng, v.v.).
Paul Tomblin

15
Theo dõi nhanh, tại sao không có chức năng có thể trả lại kích thước theo cách miễn phí?
viki.omega9

5
Chà, nếu bạn có thể đảm bảo rằng hàm chỉ được gọi với bộ nhớ malloced và thư viện theo dõi bộ nhớ malloced theo cách mà hầu hết tôi đã thấy (bằng cách sử dụng một int trước con trỏ được trả về) thì bạn có thể viết một. Nhưng nếu con trỏ đến một mảng tĩnh hoặc tương tự, nó sẽ thất bại. Tương tự, không có gì đảm bảo rằng kích thước của bộ nhớ malloced có thể truy cập được vào chương trình của bạn.
Paul Tomblin

9
@ viki.omega9: Một điều cần lưu ý là kích thước được ghi lại bởi malloc / hệ thống miễn phí có thể không phải là kích thước bạn yêu cầu. Bạn malloc 9 byte và nhận được 16. Malloc 3K byte và nhận 4K. Hoặc tình huống tương tự.
Zan Lynx

85

Câu trả lời là không."

Những gì lập trình viên C làm là lưu trữ kích thước của mảng ở đâu đó. Nó có thể là một phần của cấu trúc hoặc lập trình viên có thể gian lận một chút và malloc()nhiều bộ nhớ hơn so với yêu cầu để lưu trữ giá trị độ dài trước khi bắt đầu mảng.


3
Đó là cách các chuỗi pascal được triển khai
dsm

6
và rõ ràng chuỗi pascal là lý do tại sao excel chạy quá nhanh!
Adam Naylor

8
@Adam: Nhanh quá. Tôi sử dụng nó trong một danh sách các chuỗi thực hiện của tôi. Nó cực nhanh để tìm kiếm tuyến tính bởi vì nó là: kích thước tải, tải trước pos + kích thước, so sánh kích thước với kích thước tìm kiếm, nếu strncmp bằng nhau, di chuyển đến chuỗi tiếp theo, lặp lại. Nó nhanh hơn tìm kiếm nhị phân lên tới khoảng 500 chuỗi.
Zan Lynx

47

Đối với mảng động ( malloc hoặc C ++ mới ), bạn cần lưu trữ kích thước của mảng như được đề cập bởi người khác hoặc có thể xây dựng cấu trúc trình quản lý mảng xử lý thêm, xóa, đếm, v.v. Thật không may C không làm điều này gần như C ++ vì về cơ bản bạn phải xây dựng nó cho từng loại mảng khác nhau mà bạn đang lưu trữ sẽ cồng kềnh nếu bạn có nhiều loại mảng mà bạn cần quản lý.

Đối với mảng tĩnh, chẳng hạn như mảng trong ví dụ của bạn, có một macro phổ biến được sử dụng để lấy kích thước, nhưng đó là không được khuyến khích vì nó không kiểm tra xem tham số có thực sự là mảng tĩnh hay không. Macro được sử dụng trong mã thực, ví dụ, trong các tiêu đề nhân Linux mặc dù nó có thể hơi khác so với bên dưới:

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

Bạn có thể google vì những lý do để cảnh giác với các macro như thế này. Hãy cẩn thận.

Nếu có thể, stdlib C ++ như vector sẽ an toàn hơn và dễ sử dụng hơn.


11
ARRAY_SIZE là một mô hình phổ biến được sử dụng bởi các lập trình viên thực tế ở khắp mọi nơi.
Sanjaya R

5
Vâng, đó là một mô hình phổ biến. Bạn vẫn cần sử dụng nó một cách thận trọng mặc dù rất dễ quên và sử dụng nó trên một mảng động.
Ryan

2
Vâng, điểm tốt, nhưng câu hỏi đang được hỏi là về con trỏ một, không phải là mảng tĩnh.
Paul Tomblin

2
ARRAY_SIZEMacro đó luôn hoạt động nếu đối số của nó là một mảng (tức là biểu thức của kiểu mảng). Đối với cái gọi là "mảng động" của bạn, bạn không bao giờ có được một "mảng" thực sự (biểu thức của kiểu mảng). (Tất nhiên, bạn không thể, vì các kiểu mảng bao gồm kích thước của chúng tại thời gian biên dịch.) Bạn chỉ cần lấy một con trỏ đến phần tử đầu tiên. Phản đối của bạn "không kiểm tra xem tham số có thực sự là một mảng tĩnh không" không thực sự hợp lệ, vì chúng khác nhau vì một là một mảng và khác là không.
newacct

2
Có một hàm mẫu nổi xung quanh làm điều tương tự nhưng sẽ ngăn việc sử dụng các con trỏ.
Natalie Adams

18

Có một giải pháp sạch với các mẫu C ++, không sử dụng sizeof () . Hàm getSize () sau đây trả về kích thước của bất kỳ mảng tĩnh nào:

#include <cstddef>

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

Đây là một ví dụ với cấu trúc 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;
}

Đầu ra:

3
5

Tôi chưa bao giờ thấy ký hiệu T (&)[SIZE]. Bạn có thể giải thích điều này có nghĩa là gì? Ngoài ra, bạn có thể đề cập đến constexpr trong bối cảnh này.
WorldSEnder

2
Thật tuyệt nếu bạn sử dụng c ++ và bạn thực sự có một biến kiểu. Không ai trong số họ là trường hợp trong câu hỏi: Ngôn ngữ là C và điều OP muốn lấy kích thước mảng là một con trỏ đơn giản.
Oguk

mã này sẽ dẫn đến sự phình to mã bằng cách tạo lại cùng một mã cho mỗi kết hợp kích thước / loại khác nhau hoặc được tối ưu hóa một cách kỳ diệu bởi trình biên dịch?
dùng2796283

@WorldSEnder: Đó là cú pháp C ++ để tham chiếu kiểu mảng (không có tên biến, chỉ có kích thước và kiểu phần tử).
Peter Cordes

@ user2796283: Chức năng này được tối ưu hóa hoàn toàn vào thời gian biên dịch; không có phép thuật là cần thiết; nó không kết hợp bất cứ điều gì với một định nghĩa duy nhất, nó chỉ đơn giản là đưa nó vào một hằng số thời gian biên dịch. (Nhưng trong bản dựng gỡ lỗi, vâng, bạn có một loạt các hàm riêng biệt trả về các hằng số khác nhau. Phép thuật liên kết có thể hợp nhất các hàm sử dụng cùng một hằng số. Người gọi không vượt qua SIZEnhư một đối số, đó là một tham số mẫu có để được biết đến bởi định nghĩa hàm.)
Peter Cordes

5

Đối với ví dụ cụ thể này, có, có, NẾU bạn sử dụng typedefs (xem bên dưới). Tất nhiên, nếu bạn làm theo cách này, bạn cũng sẽ sử dụng SIZEOF_DAYS, vì bạn biết con trỏ đang trỏ đến cái gì.

Nếu bạn có một con trỏ (void *), như được trả về bởi malloc () hoặc tương tự, thì, không, không có cách nào để xác định cấu trúc dữ liệu mà con trỏ trỏ đến và do đó, không có cách nào để xác định kích thước của nó.

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

Đầu ra:

SIZEOF_DAYS:  20
sizeof(days): 20
sizeof(*ptr): 20
sizeof(ptr):  4

5

Như tất cả các câu trả lời đúng đã nêu, bạn không thể lấy thông tin này từ giá trị con trỏ bị phân rã của mảng. Nếu con trỏ bị phân rã là đối số mà hàm nhận được, thì kích thước của mảng khởi tạo phải được cung cấp theo một cách khác để hàm biết được kích thước đó.

Đây là một gợi ý khác với những gì đã được cung cấp cho đến nay, nó sẽ hoạt động: Thay thế một con trỏ đến mảng. Đề xuất này tương tự như các đề xuất kiểu C ++, ngoại trừ C không hỗ trợ các mẫu hoặc tham chiếu:

#define ARRAY_SZ 10

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

Tuy nhiên, đề xuất này là loại ngớ ngẩn cho vấn đề của bạn, vì hàm được xác định để biết chính xác kích thước của mảng được truyền vào (do đó, không có nhu cầu sử dụng sizeof ở tất cả trên mảng). Những gì nó làm, mặc dù, là cung cấp một số loại an toàn. Nó sẽ cấm bạn đi qua trong một mảng có kích thước không mong muốn.

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

Nếu hàm được cho là có thể hoạt động trên bất kỳ kích thước nào của mảng, thì bạn sẽ phải cung cấp kích thước cho hàm dưới dạng thông tin bổ sung.


1
+1 cho "Bạn không thể lấy thông tin này từ giá trị con trỏ bị phân rã của mảng" và cung cấp cách giải quyết.
Tối đa

4

Không có giải pháp kỳ diệu. C không phải là ngôn ngữ phản chiếu. Các đối tượng không tự động biết chúng là gì.

Nhưng bạn có nhiều sự lựa chọn:

  1. Rõ ràng, thêm một tham số
  2. Kết thúc cuộc gọi trong macro và tự động thêm một tham số
  3. Sử dụng một đối tượng phức tạp hơn. Xác định cấu trúc chứa mảng động và kích thước của mảng. Sau đó, chuyển địa chỉ của cấu trúc.

Đối tượng biết chúng là gì. Nhưng nếu bạn trỏ đến một tiểu dự án, không có cách nào để có được thông tin về đối tượng hoàn chỉnh hoặc một tiểu dự án lớn hơn
MM

2

Giải pháp của tôi cho vấn đề này là lưu chiều dài của mảng vào Mảng cấu trúc dưới dạng thông tin meta về mảng.

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

Nhưng bạn phải quan tâm đến việc đặt đúng độ dài của mảng bạn muốn lưu trữ, bởi vì không có cách nào để kiểm tra độ dài này, giống như bạn bè của chúng tôi giải thích ồ ạt.


2

Bạn có thể làm một cái gì đó như thế này:

int days[] = { /*length:*/5, /*values:*/ 1,2,3,4,5 };
int *ptr = days + 1;
printf("array length: %u\n", ptr[-1]);
return 0;

1

Không, bạn không thể sử dụng sizeof(ptr)để tìm kích thước của mảng ptrđang trỏ tới.

Mặc dù phân bổ thêm bộ nhớ (nhiều hơn kích thước của mảng) sẽ hữu ích nếu bạn muốn lưu trữ độ dài trong không gian thêm.


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

Kích thước ngày [] là 20, không có yếu tố * kích thước của loại dữ liệu. Trong khi kích thước của con trỏ là 4 cho dù nó đang trỏ đến cái gì. Bởi vì một con trỏ trỏ đến phần tử khác bằng cách lưu trữ địa chỉ của nó.


1
sizeof (ptr) là kích thước của con trỏ và sizeof (* ptr) là kích thước của con trỏ tới
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

mảng_size đang chuyển đến biến kích thước :

#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

Cách sử dụng là:

void main() {

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

        array1.array[i] *= 2;
    }
}

0

Trong chuỗi có một '\0'ký tự ở cuối nên độ dài của chuỗi có thể được sử dụng các hàm như strlen. Ví dụ, vấn đề với một mảng số nguyên là bạn không thể sử dụng bất kỳ giá trị nào làm giá trị cuối nên một giải pháp có thể là giải quyết mảng và sử dụng làm giá trị cuối cho NULLcon trỏ.

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

Mã của bạn chứa đầy các bình luận, nhưng tôi nghĩ nó sẽ giúp mọi thứ dễ dàng hơn nếu bạn thêm một số giải thích chung về cách thức hoạt động bên ngoài mã, như văn bản bình thường. Bạn có thể vui lòng chỉnh sửa câu hỏi của bạn và làm điều đó? Cảm ơn bạn!
Fabio nói Phục hồi lại

Tạo một mảng các con trỏ tới từng thành phần để bạn có thể tìm kiếm tuyến tính cho nó NULLcó lẽ là sự thay thế ít hiệu quả nhất có thể tưởng tượng để chỉ lưu trữ một cách sizetrực tiếp riêng biệt . Đặc biệt là nếu bạn thực sự sử dụng lớp bổ sung này mọi lúc.
Peter Cordes
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.