2009-04-06 91 views
13

Tôi đang sử dụng macro sau để tính kích thước của một mảng:Tính toán kích thước của một mảng

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

Tuy nhiên tôi nhìn thấy sự sai lệch trong giá trị tính bằng nó khi tôi đánh giá kích thước của một mảng trong một chức năng (giá trị không chính xác được tính) trái ngược với nơi hàm được gọi (giá trị chính xác được tính toán). Mã + đầu ra bên dưới. Bất kỳ suy nghĩ, gợi ý, mẹo và cộng sự. chào đón.

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

Output:

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

Gọi thông số foo cùng tên đang làm cho điều này phản trực giác đối với bạn. Xem xét cho một tên khác mô phỏng những gì trình biên dịch đang xem xét. – ojblass

Trả lời

12

Đây không phải đang làm việc vì sizeof được tính tại thời gian biên dịch. Hàm này không có thông tin về kích thước tham số của nó (nó chỉ biết rằng nó trỏ đến một địa chỉ bộ nhớ).

Cân nhắc sử dụng vector STL thay thế hoặc chuyển các kích thước mảng làm tham số cho hàm.

1

Bạn chỉ nên gọi sizeof trên mảng. Khi bạn gọi sizeof trên loại con trỏ kích thước sẽ luôn luôn là 4 (hoặc 8, hoặc bất cứ điều gì hệ thống của bạn không).

Ký hiệu Hungary của MSFT có thể xấu xí, nhưng nếu bạn sử dụng nó, bạn không được gọi macro của bạn trên bất kỳ thứ gì bắt đầu bằng 'p'.

Đồng thời kiểm tra định nghĩa macro ARRAYSIZE() trong WinNT.h. Nếu bạn đang sử dụng C++ bạn có thể làm những điều kỳ lạ với các mẫu để có được xác nhận thời gian biên dịch nếu làm theo cách đó.

+0

Bạn có thể gọi sizeof() trên con trỏ, nhưng kích thước bạn nhận được sẽ là kích thước của con trỏ chứ không phải kích thước của mảng. –

+0

Điểm tốt. Hãy để tôi sửa lỗi đó. –

2

Nếu bạn thay đổi foo funciton một chút nó có thể làm cho bạn cảm thấy một chút thoải mái hơn:

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

Đó là những gì các trình biên dịch sẽ thấy cái gì đó là hoàn toàn một bối cảnh khác với hàm.

27

Đó là vì kích thước của int * là kích thước của một con trỏ int (4 hoặc 8 byte trên nền tảng hiện đại mà tôi sử dụng nhưng nó hoàn toàn phụ thuộc vào nền tảng). Các sizeof được tính toán tại thời gian biên dịch, không chạy thời gian, vì vậy ngay cả sizeof (arr[]) sẽ không giúp đỡ vì bạn có thể gọi hàm foo() khi chạy với nhiều mảng có kích thước khác nhau.

Kích thước của mảng int là kích thước của một mảng int.

Đây là một trong những bit phức tạp trong C/C++ - việc sử dụng mảng và con trỏ không phải lúc nào cũng giống nhau. Mảng sẽ trong nhiều trường hợp, phân rã thành con trỏ đến phần tử đầu tiên của mảng đó.

Có ít nhất hai giải pháp, tương thích với cả C và C++:

  • vượt qua chiều dài với mảng (không rằng hữu ích nếu mục đích của chức năng là để thực sự làm việc ra mảng kích thước).
  • chuyển giá trị sentinel đánh dấu phần cuối của dữ liệu, ví dụ: {1,2,3,4,-1}.
4

Lưu ý rằng ngay cả khi bạn cố gắng để nói với trình biên dịch C kích thước của mảng trong hàm, nó không mất gợi ý (DIM của tôi là tương đương với G_N_ELEMENTS của bạn):

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

Bản in này:

array1: size = 1 
array2: size = 1 

Nếu bạn muốn biết mảng lớn nằm trong hàm, hãy chuyển kích thước cho hàm. Hoặc, trong C++, hãy sử dụng những thứ như STL vector<int>.

+0

+1. Thực tế là C + + ít nhất (và có thể C, tôi không chắc chắn) không tôn trọng kích thước được đưa ra, hoặc thậm chí đưa ra một cảnh báo/lỗi, là IMHO khá đáng trách. –

+0

Vâng, tôi đã tự hỏi về sizeof (đầy đủ-array-type parameters) bản thân mình. – 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) là sizeof (int *), tức là. 4

Trừ khi bạn có lý do rất tốt để viết mã như thế này, KHÔNG. Chúng ta đang ở thế kỷ 21, sử dụng std :: vector để thay thế.

Mọi chi tiết, thấy C++ Hỏi đáp: http://www.parashift.com/c++-faq-lite/containers.html

Hãy nhớ rằng: "Mảng là ác"

+0

Không nên là: "chúng ta đang ở thế kỷ 21 bây giờ, không cần phải sử dụng C++ nữa" ;-) – JesperE

+0

Bạn có cái gì phù hợp hơn ...? :-) –

10

Trong C++, bạn có thể xác định G_N_ELEMENTS như thế này:

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

Nếu bạn muốn sử dụng kích thước mảng tại thời gian biên dịch, dưới đây là cách thực hiện:

// ArraySize 
template<typename T> 
struct ArraySize; 

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

Cảm ơn j_random_hacker để sửa lỗi của tôi và cung cấp thêm thông tin.

+0

Đây là định nghĩa thay thế tuyệt vời sẽ không biên dịch nếu nó được chuyển qua một con trỏ. +1 –

+0

Mặc dù, tôi muốn đặt tham số thành tham chiếu const. –

+1

Mặc dù bạn có thể cần giá trị tại thời gian biên dịch, trong trường hợp đó một lựa chọn tốt hơn là: template struct G_N_ELEMENTS; mẫu struct G_N_ELEMENTS {static size_t value = N; }; –

4

Edit: C++ 11 đã được giới thiệu từ câu trả lời này đã được viết, và nó bao gồm các chức năng để thực hiện chính xác những gì tôi hiển thị bên dưới: std::beginstd::end. Các phiên bản Const std::cbeginstd::cend cũng đi vào một phiên bản tương lai của tiêu chuẩn (C++ 14?) Và có thể đã có trong trình biên dịch của bạn rồi. Thậm chí không cân nhắc việc sử dụng các chức năng của tôi bên dưới nếu bạn có quyền truy cập vào các chức năng tiêu chuẩn.


Tôi muốn xây dựng một chút trên Benoît's answer. Thay vì chỉ chuyển địa chỉ bắt đầu của mảng dưới dạng con trỏ, hoặc con trỏ cộng với kích thước như những người khác đã gợi ý, hãy lấy một gợi ý từ thư viện chuẩn và chuyển hai con trỏ tới đầu và cuối của mảng đó. Điều này không chỉ làm cho mã của bạn giống như C++ hiện đại hơn, mà bạn có thể sử dụng bất kỳ thuật toán thư viện chuẩn nào trên mảng của bạn!

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

Đây là định nghĩa thay thế cho BEGIN và END, nếu các mẫu không hoạt động.

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

Cập nhật: Đoạn mã trên với các mẫu làm việc trong MS VC++ 2005 và GCC 3.4.6, như nó phải. Tôi cần phải có một trình biên dịch mới.

Tôi cũng đang xem xét lại quy ước đặt tên được sử dụng tại đây - các chức năng mẫu giả mạo khi macro chỉ cảm thấy sai. Tôi chắc chắn rằng tôi sẽ sử dụng điều này trong mã của riêng tôi đôi khi sớm, và tôi nghĩ rằng tôi sẽ sử dụng ArrayBegin, ArrayEnd, ArrayConstBegin, và ArrayConstEnd.

+0

+1, ý tưởng hay, nhưng hãy thay đổi "T [N] & mảng" (không phải là loại C++ hợp lệ và sẽ không biên dịch) thành "T (& mảng) [N]". –

+0

@j_random_hacker: Bạn có thể giải thích tại sao T [N] & không phải là loại C++ hợp lệ không? –

+0

@Benoit: Đó chỉ là quy tắc của cú pháp C/C++ - chúng yêu cầu các giới hạn mảng * sau * tên định danh (được ngụ ý ở đây). Trong cùng một cách, để khai báo một mảng gồm 10 int được gọi là foo, bạn cần viết "int foo [10];" không phải là "int [10] foo;" - mặc dù sau này có vẻ hợp lý, nó sẽ không phân tích cú pháp. –

0

Bây giờ chúng tôi có constexpr trong C++ 11, loại phiên bản an toàn (không có macro) cũng có thể được sử dụng trong biểu thức không đổi.

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

Điều này sẽ không biên dịch ở nơi nó không hoạt động bình thường, không giống như giải pháp macro của bạn). Bạn có thể sử dụng nó ở đâu một hằng số thời gian biên dịch được yêu cầu:

int new_array[array_size(some_other_array)]; 

Điều đó đang được nói, bạn nên sử dụng std::array cho điều này nếu có thể. Không chú ý đến những người nói sử dụng std::vector vì nó tốt hơn. std::vector là một cấu trúc dữ liệu khác với các điểm mạnh khác nhau. std::array không có chi phí so với một mảng kiểu C, nhưng không giống như mảng kiểu C, nó sẽ không phân rã thành con trỏ ở kích thích nhỏ nhất. std::vector, mặt khác, yêu cầu tất cả các truy cập phải được truy cập gián tiếp (đi qua một con trỏ) và sử dụng nó đòi hỏi phải phân bổ động. Một điều cần lưu ý nếu bạn đang sử dụng để sử dụng mảng C-phong cách là phải chắc chắn để vượt qua std::array đến một chức năng như thế này:

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

Nếu bạn không vượt qua bằng cách tham khảo, dữ liệu được sao chép. Điều này tuân theo hành vi của hầu hết các kiểu được thiết kế tốt, nhưng khác với các mảng kiểu C khi được truyền vào một hàm (nó giống như hành vi của một mảng kiểu C bên trong một cấu trúc).

Các vấn đề liên quan