Làm cách nào để xác định kích thước của mảng trong C?


Câu trả lời:


1260

Tóm tắt điều hành:

int a[17];
size_t n = sizeof(a)/sizeof(a[0]);

Câu trả lời đầy đủ:

Để xác định kích thước của mảng theo byte, bạn có thể sử dụng sizeof toán tử:

int a[17];
size_t n = sizeof(a);

Trên máy tính của tôi, ints dài 4 byte, vì vậy n là 68.

Để xác định số lượng phần tử trong mảng, chúng ta có thể chia tổng kích thước của mảng cho kích thước của phần tử mảng. Bạn có thể làm điều này với loại, như thế này:

int a[17];
size_t n = sizeof(a) / sizeof(int);

và nhận được câu trả lời thích hợp (68/4 = 17), nhưng nếu loại athay đổi, bạn sẽ gặp một lỗi khó chịu nếu bạn quên thay đổi sizeof(int).

Vì vậy, ước số được ưu tiên là sizeof(a[0])hoặc tương đương sizeof(*a), kích thước của phần tử đầu tiên của mảng.

int a[17];
size_t n = sizeof(a) / sizeof(a[0]);

Một ưu điểm khác là bây giờ bạn có thể dễ dàng tham số hóa tên mảng trong macro và nhận:

#define NELEMS(x)  (sizeof(x) / sizeof((x)[0]))

int a[17];
size_t n = NELEMS(a);

6
Mã được tạo sẽ giống hệt nhau, vì trình biên dịch biết loại * int_arr tại thời gian biên dịch (và do đó giá trị của sizeof (* int_arr)). Nó sẽ là một hằng số, và trình biên dịch có thể tối ưu hóa tương ứng.
Đánh dấu Harrison

10
Nó phải là trường hợp với tất cả các trình biên dịch, vì kết quả của sizeof được định nghĩa là hằng số thời gian biên dịch.
Đánh dấu Harrison

451
Quan trọng : Đừng dừng đọc ở đây, đọc câu trả lời tiếp theo! Điều này chỉ hoạt động cho các mảng trên ngăn xếp , ví dụ: nếu bạn đang sử dụng malloc () hoặc truy cập một tham số chức năng, bạn sẽ không gặp may. Xem bên dưới.
Markus

7
Đối với lập trình API Windows trong C hoặc C ++, có ARRAYSIZEmakro được xác định trong WinNT.h(được kéo bởi các tiêu đề khác). Vì vậy, người dùng WinAPI không cần xác định makro của riêng họ.
Lumi

17
@Markus nó hoạt động cho bất kỳ biến nào có kiểu mảng; điều này không phải là "trên ngăn xếp". Ví dụ static int a[20];. Nhưng nhận xét của bạn rất hữu ích cho những người đọc có thể không nhận ra sự khác biệt giữa một mảng và một con trỏ.
MM

808

Các sizeofcách là con đường đúng khi và chỉ khi bạn đang đối phó với mảng chưa nhận được như các thông số. Một mảng được gửi dưới dạng tham số cho hàm được coi là một con trỏ, do đó sizeofsẽ trả về kích thước của con trỏ, thay vì của mảng.

Vì vậy, bên trong các chức năng phương pháp này không hoạt động. Thay vào đó, luôn luôn vượt qua một tham số bổ sung size_t sizecho biết số lượng phần tử trong mảng.

Kiểm tra:

#include <stdio.h>
#include <stdlib.h>

void printSizeOf(int intArray[]);
void printLength(int intArray[]);

int main(int argc, char* argv[])
{
    int array[] = { 0, 1, 2, 3, 4, 5, 6 };

    printf("sizeof of array: %d\n", (int) sizeof(array));
    printSizeOf(array);

    printf("Length of array: %d\n", (int)( sizeof(array) / sizeof(array[0]) ));
    printLength(array);
}

void printSizeOf(int intArray[])
{
    printf("sizeof of parameter: %d\n", (int) sizeof(intArray));
}

void printLength(int intArray[])
{
    printf("Length of parameter: %d\n", (int)( sizeof(intArray) / sizeof(intArray[0]) ));
}

Đầu ra (trong HĐH Linux 64 bit):

sizeof of array: 28
sizeof of parameter: 8
Length of array: 7
Length of parameter: 2

Đầu ra (trong hệ điều hành windows 32 bit):

sizeof of array: 28
sizeof of parameter: 4
Length of array: 7
Length of parameter: 1

10
Tại sao length of parameter:2nếu chỉ một con trỏ đến phần tử mảng 1 được thông qua?
Bbvarghe

16
@Bbvarghe Đó là vì các con trỏ trong các hệ thống 64 bit là 8 byte (sizeof (intArray)), nhưng ints vẫn (thường) dài 4 byte (sizeof (intArray [0])).
Elideb

13
@Pacerier: Không có mã chính xác - giải pháp thông thường là chuyển chiều dài cùng với mảng làm đối số riêng.
Jean Hominal

10
Đợi đã, vậy không có cách nào để truy cập mảng trực tiếp từ một con trỏ và xem kích thước của nó? Mới đến C đây.
sudo

7
@Michael Trouw: bạn có thể sử dụng cú pháp toán tử nếu điều đó làm bạn cảm thấy tốt hơn : (sizeof array / sizeof *array).
chqrlie

134

Điều đáng chú ý là sizeofkhông giúp ích gì khi xử lý một giá trị mảng đã phân rã thành một con trỏ: mặc dù nó trỏ đến phần bắt đầu của một mảng, nhưng trình biên dịch nó giống như một con trỏ tới một phần tử của mảng đó . Một con trỏ không "nhớ" bất cứ điều gì khác về mảng đã được sử dụng để khởi tạo nó.

int a[10];
int* p = a;

assert(sizeof(a) / sizeof(a[0]) == 10);
assert(sizeof(p) == sizeof(int*));
assert(sizeof(*p) == sizeof(int));

1
@ Magnus: Tiêu chuẩn định nghĩa sizeof là thu được số byte trong đối tượng và sizeof (char) luôn là một. Số lượng bit trong một byte là cụ thể thực hiện. Chỉnh sửa: Phần tiêu chuẩn ANSI C ++ 5.3.3 Sizeof: "Toán tử sizeof mang lại số byte trong biểu diễn đối tượng của toán hạng của nó. [...] sizeof (char), sizeof (char đã ký) và sizeof (char không dấu) là 1; kết quả của sizeof được áp dụng cho bất kỳ loại cơ bản nào khác được xác định theo triển khai. "
Skizz

Mục 1.6 Mô hình bộ nhớ C ++: "Đơn vị lưu trữ cơ bản trong mô hình bộ nhớ C ++ là byte. Một byte ít nhất đủ lớn để chứa bất kỳ thành viên nào của bộ ký tự thực hiện cơ bản và bao gồm một chuỗi bit liền kề, số trong đó được xác định theo cách thực hiện. "
Skizz

2
Tôi nhớ rằng CRAY có C với char32 bit. Tất cả các tiêu chuẩn nói là các giá trị nguyên từ 0 đến 127 có thể được biểu diễn và phạm vi của nó ít nhất là -127 đến 127 (ký hiệu char) hoặc 0 đến 255 (ký tự không dấu).
vonbrand

5
Đây là một phản ứng tuyệt vời. Tôi muốn nhận xét rằng tất cả các xác nhận ở trên được đánh giá là TRUE.
Javad

49

"Thủ thuật" sizeof là cách tốt nhất mà tôi biết, với một điều nhỏ nhưng (với tôi, đây là một tiểu cảnh thú cưng lớn) thay đổi quan trọng trong việc sử dụng dấu ngoặc đơn.

Vì mục nhập Wikipedia cho thấy rõ, C's sizeofkhông phải là một chức năng; đó là một nhà điều hành . Vì vậy, nó không yêu cầu dấu ngoặc đơn xung quanh đối số của nó, trừ khi đối số là tên loại. Điều này rất dễ nhớ, vì nó làm cho đối số trông giống như một biểu thức truyền, cũng sử dụng dấu ngoặc đơn.

Vì vậy: Nếu bạn có những điều sau đây:

int myArray[10];

Bạn có thể tìm thấy số lượng phần tử với mã như thế này:

size_t n = sizeof myArray / sizeof *myArray;

Điều đó, với tôi, đọc dễ dàng hơn nhiều so với thay thế bằng dấu ngoặc đơn. Tôi cũng thích sử dụng dấu hoa thị ở phần bên phải của bộ phận, vì nó ngắn gọn hơn là lập chỉ mục.

Tất nhiên, đây cũng là thời gian biên dịch, vì vậy không cần phải lo lắng về việc phân chia ảnh hưởng đến hiệu suất của chương trình. Vì vậy, sử dụng hình thức này bất cứ nơi nào bạn có thể.

Tốt nhất là luôn sử dụng sizeof trên một đối tượng thực tế khi bạn có một đối tượng, thay vì trên một loại, từ đó bạn không cần phải lo lắng về việc gây ra lỗi và nêu sai loại.

Chẳng hạn, giả sử bạn có một hàm xuất một số dữ liệu dưới dạng luồng byte, ví dụ trên toàn mạng. Chúng ta hãy gọi hàm send()và làm cho nó lấy làm đối số một con trỏ tới đối tượng cần gửi và số byte trong đối tượng. Vì vậy, nguyên mẫu trở thành:

void send(const void *object, size_t size);

Và sau đó bạn cần gửi một số nguyên, vì vậy bạn mã hóa nó như thế này:

int foo = 4711;
send(&foo, sizeof (int));

Bây giờ, bạn đã giới thiệu một cách tinh tế để tự bắn vào chân mình, bằng cách chỉ định loại fooở hai nơi. Nếu cái này thay đổi nhưng cái kia thì không, mã bị hỏng. Vì vậy, luôn luôn làm điều đó như thế này:

send(&foo, sizeof foo);

Bây giờ bạn được bảo vệ. Chắc chắn, bạn nhân đôi tên của biến, nhưng điều đó có xác suất phá vỡ cao theo cách trình biên dịch có thể phát hiện, nếu bạn thay đổi nó.


Btw, họ có hướng dẫn giống hệt nhau ở cấp độ bộ xử lý? Có sizeof(int)yêu cầu hướng dẫn ít hơn sizeof(foo)?
Pacerier

@Pacerier: không, chúng giống hệt nhau. Nghĩ đến int x = 1+1;so với int x = (1+1);. Ở đây, dấu ngoặc đơn hoàn toàn chỉ là thẩm mỹ.
quetzalcoatl

@Aidiakapi Điều đó không đúng, hãy xem xét C99 VLAs.
thư giãn

@unwind Cảm ơn, tôi đứng sửa. Để sửa nhận xét của tôi, sizeofsẽ luôn là hằng số trong C ++ và C89. Với mảng chiều dài thay đổi của C99, nó có thể được đánh giá trong thời gian chạy.
Aidiakapi

2
sizeofcó thể là một nhà điều hành nhưng nó nên được coi là một chức năng theo Linus Torvalds. Tôi đồng ý. Đọc lý lẽ của anh ấy ở đây: lkml.org/lkml/2012/7/11/103
Tiến sĩ Person Person II

37
int size = (&arr)[1] - arr;

Kiểm tra liên kết này để giải thích


7
Nhỏ nitpick: kết quả của phép trừ con trỏ có loại ptrdiff_t. (Thông thường trên hệ thống 64 bit, đây sẽ là loại lớn hơn int). Ngay cả khi bạn thay đổi intthành ptrdiff_tmã này, nó vẫn có lỗi nếu arrchiếm hơn một nửa không gian địa chỉ.
MM

2
@MM Một nitlog nhỏ khác: Tùy thuộc vào kiến ​​trúc hệ thống của bạn, không gian địa chỉ không lớn bằng kích thước con trỏ trên hầu hết các hệ thống. Ví dụ, Windows giới hạn không gian địa chỉ cho các ứng dụng 64 bit thành 8TB hoặc 44 bit. Vì vậy, ngay cả khi bạn có một mảng lớn hơn một nửa không gian địa chỉ 4.1TB của mình, thì đó sẽ không phải là một lỗi. Chỉ khi không gian địa chỉ của bạn vượt quá 63 bit trên các hệ thống đó, bạn mới có thể gặp phải lỗi đó. Nói chung, đừng lo lắng về nó.
Aidiakapi

1
@Aidiakapi trên 32-bit x86 Linux hoặc trên Windows với /3Gtùy chọn bạn có phân chia người dùng / hạt nhân 3G / 1G, cho phép bạn có kích thước mảng lên tới 75% kích thước không gian địa chỉ.
Ruslan

1
Coi foo buf1[80]; foo buf2[sizeof buf1/sizeof buf1[0]]; foo buf3[(&buf1)[1] - buf1];như là biến toàn cầu. buf3[]tuyên bố thất bại như (&buf1)[1] - buf1không phải là một hằng số.
chux - Phục hồi Monica

2
Đây là hành vi không được xác định về mặt kỹ thuật vì tiêu chuẩn rõ ràng không cho phép hội thảo kết thúc trước khi kết thúc một mảng (ngay cả khi bạn không cố đọc giá trị được lưu trữ)
MM


21

Nếu bạn biết kiểu dữ liệu của mảng, bạn có thể sử dụng một cái gì đó như:

int arr[] = {23, 12, 423, 43, 21, 43, 65, 76, 22};

int noofele = sizeof(arr)/sizeof(int);

Hoặc nếu bạn không biết kiểu dữ liệu của mảng, bạn có thể sử dụng một cái gì đó như:

noofele = sizeof(arr)/sizeof(arr[0]);

Lưu ý: Điều này chỉ hoạt động nếu mảng không được xác định trong thời gian chạy (như malloc) và mảng không được truyền trong hàm. Trong cả hai trường hợp, arr(tên mảng) là một con trỏ.


4
int noofele = sizeof(arr)/sizeof(int);chỉ là một nửa tốt hơn so với mã hóa int noofele = 9;. Sử dụng sizeof(arr)duy trì tính linh hoạt nên kích thước mảng thay đổi. Tuy nhiên, sizeof(int)cần một bản cập nhật nên loại arr[]thay đổi. Tốt hơn để sử dụng sizeof(arr)/sizeof(arr[0])ngay cả khi loại được biết đến. Không rõ tại sao sử dụng intcho noofelevs. size_t, loại được trả về bởi sizeof().
chux - Phục hồi Monica

19

Macro ARRAYELEMENTCOUNT(x)mà mọi người đang sử dụng đánh giá không chính xác . Trên thực tế, đây chỉ là một vấn đề nhạy cảm, bởi vì bạn không thể có các biểu thức dẫn đến loại 'mảng'.

/* Compile as: CL /P "macro.c" */
# define ARRAYELEMENTCOUNT(x) (sizeof (x) / sizeof (x[0]))

ARRAYELEMENTCOUNT(p + 1);

Thực tế đánh giá là:

(sizeof (p + 1) / sizeof (p + 1[0]));

Trong khi

/* Compile as: CL /P "macro.c" */
# define ARRAYELEMENTCOUNT(x) (sizeof (x) / sizeof (x)[0])

ARRAYELEMENTCOUNT(p + 1);

Nó đánh giá chính xác để:

(sizeof (p + 1) / sizeof (p + 1)[0]);

Điều này thực sự không liên quan nhiều đến kích thước của mảng một cách rõ ràng. Tôi vừa nhận thấy rất nhiều lỗi từ việc không thực sự quan sát cách bộ xử lý C hoạt động. Bạn luôn bao bọc tham số macro, không thể tham gia vào biểu thức.


Chính xác; ví dụ của tôi là một điều xấu. Nhưng đó thực sự là chính xác những gì sẽ xảy ra. Như tôi đã đề cập trước đây p + 1sẽ kết thúc dưới dạng một con trỏ và làm mất hiệu lực toàn bộ macro (giống như khi bạn cố gắng sử dụng macro trong một hàm với tham số con trỏ).

Vào cuối ngày, trong trường hợp cụ thể này , lỗi không thực sự quan trọng (vì vậy tôi chỉ lãng phí thời gian của mọi người; huzzah!), Bởi vì bạn không có biểu thức với một loại 'mảng'. Nhưng thực sự quan điểm về các đánh giá tiền xử lý tôi nghĩ là một điều quan trọng.


2
Cảm ơn đã giải thích. Phiên bản gốc dẫn đến lỗi thời gian biên dịch. Clang báo cáo "giá trị được đăng ký không phải là một mảng, con trỏ hoặc vectơ". Điều này có vẻ thích hợp hơn trong trường hợp này, mặc dù ý kiến ​​của bạn về thứ tự đánh giá trong macro được thực hiện tốt.
Đánh dấu Harrison

1
Tôi đã không nghĩ về khiếu nại của trình biên dịch là một thông báo tự động của một loại không chính xác. Cảm ơn bạn!

3
Có một lý do để không sử dụng (sizeof (x) / sizeof (*x))?
nghiêm túc

16

Đối với mảng nhiều chiều, nó phức tạp hơn một chút. Mọi người thường định nghĩa các hằng vĩ mô rõ ràng, nghĩa là

#define g_rgDialogRows   2
#define g_rgDialogCols   7

static char const* g_rgDialog[g_rgDialogRows][g_rgDialogCols] =
{
    { " ",  " ",    " ",    " 494", " 210", " Generic Sample Dialog", " " },
    { " 1", " 330", " 174", " 88",  " ",    " OK",        " " },
};

Nhưng các hằng số này có thể được đánh giá tại thời gian biên dịch với sizeof :

#define rows_of_array(name)       \
    (sizeof(name   ) / sizeof(name[0][0]) / columns_of_array(name))
#define columns_of_array(name)    \
    (sizeof(name[0]) / sizeof(name[0][0]))

static char* g_rgDialog[][7] = { /* ... */ };

assert(   rows_of_array(g_rgDialog) == 2);
assert(columns_of_array(g_rgDialog) == 7);

Lưu ý rằng mã này hoạt động trong C và C ++. Đối với các mảng có nhiều hơn hai chiều sử dụng

sizeof(name[0][0][0])
sizeof(name[0][0][0][0])

vv, quảng cáo vô hạn.


15
sizeof(array) / sizeof(array[0])

Phụ thuộc vào loại arraycó, bạn không cần sử dụng sizeof(array) / sizeof(array[0])nếu arraylà một mảng của một trong hai char, unsigned charhoặc signed char- Trích dẫn từ C18,6.5.3.4 / 4: "Khi sizeof được áp dụng cho toán hạng có kiểu char, char không dấu hoặc char đã ký , (hoặc một phiên bản đủ điều kiện) kết quả là 1. " Trong trường hợp này, bạn có thể chỉ cần làm sizeof(array)như được giải thích trong câu trả lời dành riêng của tôi .
RobertS hỗ trợ Monica Cellio

15

Kích thước của một mảng trong C:

int a[10];
size_t size_of_array = sizeof(a);      // Size of array a
int n = sizeof (a) / sizeof (a[0]);    // Number of elements in array a
size_t size_of_element = sizeof(a[0]); // Size of each element in array a                                          
                                       // Size of each element = size of type

4
Tò mò rằng mã được sử dụng size_t size_of_elementnhưng intvới int n = sizeof (a) / sizeof (a[0]); và khôngsize_t n = sizeof (a) / sizeof (a[0]);
Chux - Khôi phục Monica

1
Xin chào @Yogeesh HT, bạn có thể vui lòng trả lời nghi ngờ của chux. Tôi cũng rất tò mò muốn biết int n = sizeof (a) / sizeof (a [0]) đưa ra độ dài của mảng như thế nào và tại sao chúng ta không sử dụng size_t cho độ dài của mảng. Bất cứ ai có thể trả lời nó?
Não

1
@Brain sizeof (a) cung cấp sizeof của tất cả các phần tử có trong mảng một sizeof (a [0]) cho sizeof của các phần tử thứ nhất. Giả sử a = {1,2,3,4,5}; sizeof (a) = 20byte (nếu sizeof (int) = 4byte nhân 5), sizeof (a [0]) = 4byte, vì vậy 20/4 = 5 tức là không có yếu tố nào
Yogeesh HT

2
@YogeeshHT Đối với các mảng rất lớn như char a[INT_MAX + 1u];, int nđược sử dụng trong int n = sizeof (a) / sizeof (a[0]);là không đủ (đó là UB). Sử dụng size_t n = sizeof (a) / sizeof (a[0]);không phát sinh vấn đề này.
chux - Phục hồi Monica

13

Tôi khuyên bạn không bao giờ sử dụng sizeof(ngay cả khi nó có thể được sử dụng) để có được bất kỳ hai kích thước khác nhau của một mảng, về số lượng phần tử hoặc theo byte, đó là hai trường hợp cuối cùng tôi trình bày ở đây. Đối với mỗi một trong hai kích thước, các macro hiển thị bên dưới có thể được sử dụng để làm cho nó an toàn hơn. Lý do là để làm rõ ý định của mã đối với các nhà bảo trì và sự khác biệt sizeof(ptr)so sizeof(arr)với cái nhìn đầu tiên (được viết theo cách này là không rõ ràng), do đó các lỗi sau đó rõ ràng đối với mọi người đọc mã.


TL; DR:

#define ARRAY_SIZE(arr)     (sizeof(arr) / sizeof((arr)[0]) + must_be_array(arr))

#define ARRAY_SSIZE(arr)    ((ptrdiff_t)ARRAY_SIZE(arr))

#define ARRAY_BYTES(arr)    (sizeof((arr)[0]) * ARRAY_SIZE(arr))

must_be_array(arr)(được định nghĩa dưới đây) IS cần thiết -Wsizeof-pointer-divlà lỗi (kể từ tháng 4 năm 2020):

#define is_same_type(a, b)      __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(a)             (!is_same_type((a), &(a)[0]))
#define Static_assert_array(a)  _Static_assert(is_array(a), "Not a `[]` !")
#define must_be_array(a)        (                                       \
        0 * (int)sizeof(                                                \
                struct {                                                \
                        Static_assert_array(a);                         \
                        char ISO_C_forbids_a_struct_with_no_members__;  \
                }                                                       \
        )                                                               \
)

Đã có những lỗi quan trọng liên quan đến chủ đề này: https://lkml.org/lkml/2015/9/3/428

Tôi không đồng ý với giải pháp mà Linus cung cấp, đó là không bao giờ sử dụng ký hiệu mảng cho các tham số của hàm.

Tôi thích ký hiệu mảng là tài liệu cho thấy một con trỏ đang được sử dụng như một mảng. Nhưng điều đó có nghĩa là một giải pháp chống lừa đảo cần được áp dụng để không thể viết mã lỗi.

Từ một mảng, chúng ta có ba kích thước mà chúng ta có thể muốn biết:

  • Kích thước của các phần tử của mảng
  • Số lượng phần tử trong mảng
  • Kích thước tính theo byte mà mảng sử dụng trong bộ nhớ

Kích thước của các phần tử của mảng

Cái đầu tiên rất đơn giản và không thành vấn đề nếu chúng ta đang xử lý một mảng hoặc một con trỏ, bởi vì nó được thực hiện theo cùng một cách.

Ví dụ về cách sử dụng:

void foo(ptrdiff_t nmemb, int arr[static nmemb])
{
        qsort(arr, nmemb, sizeof(arr[0]), cmp);
}

qsort() cần giá trị này như là đối số thứ ba của nó.


Đối với hai kích thước khác, là chủ đề của câu hỏi, chúng tôi muốn đảm bảo rằng chúng tôi đang xử lý một mảng và phá vỡ phần biên dịch nếu không, vì nếu chúng tôi xử lý một con trỏ, chúng tôi sẽ nhận được các giá trị sai . Khi quá trình biên dịch bị hỏng, chúng ta sẽ có thể dễ dàng thấy rằng chúng ta không xử lý một mảng, nhưng thay vào đó bằng một con trỏ và chúng ta sẽ phải viết mã bằng một biến hoặc một macro lưu trữ kích thước của mảng phía sau con trỏ.


Số lượng phần tử trong mảng

Đây là câu hỏi phổ biến nhất và nhiều câu trả lời đã cung cấp cho bạn macro ARRAY_SIZE điển hình:

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

Cho rằng kết quả của ARRAY_SIZE thường được sử dụng với các biến kiểu đã ký ptrdiff_t, nên xác định một biến thể đã ký của macro này:

#define ARRAY_SSIZE(arr)    ((ptrdiff_t)ARRAY_SIZE(arr))

Các mảng có nhiều PTRDIFF_MAXthành viên sẽ cung cấp các giá trị không hợp lệ cho phiên bản macro đã ký này, nhưng từ khi đọc C17 :: 6.5.6.9, các mảng như thế đã chơi với lửa. Chỉ ARRAY_SIZEsize_tnên được sử dụng trong những trường hợp.

Các phiên bản gần đây của trình biên dịch, chẳng hạn như GCC 8, sẽ cảnh báo bạn khi bạn áp dụng macro này cho con trỏ, vì vậy nó an toàn (có các phương pháp khác để làm cho nó an toàn với trình biên dịch cũ hơn).

Nó hoạt động bằng cách chia kích thước theo byte của toàn bộ mảng cho kích thước của từng phần tử.

Ví dụ về việc sử dụng:

void foo(ptrdiff_t nmemb)
{
        char buf[nmemb];

        fgets(buf, ARRAY_SIZE(buf), stdin);
}

void bar(ptrdiff_t nmemb)
{
        int arr[nmemb];

        for (ptrdiff_t i = 0; i < ARRAY_SSIZE(arr); i++)
                arr[i] = i;
}

Nếu các hàm này không sử dụng mảng, nhưng lấy chúng làm tham số thay vào đó, mã cũ sẽ không biên dịch, do đó không thể có lỗi (do sử dụng phiên bản trình biên dịch gần đây hoặc sử dụng một số thủ thuật khác) và chúng ta cần thay thế cuộc gọi macro bằng giá trị:

void foo(ptrdiff_t nmemb, char buf[nmemb])
{

        fgets(buf, nmemb, stdin);
}

void bar(ptrdiff_t nmemb, int arr[nmemb])
{

        for (ptrdiff_t i = 0; i < nmemb; i++)
                arr[i] = i;
}

Kích thước tính theo byte mà mảng sử dụng trong bộ nhớ

ARRAY_SIZE thường được sử dụng như một giải pháp cho trường hợp trước, nhưng trường hợp này hiếm khi được viết an toàn, có thể vì nó ít phổ biến hơn.

Cách phổ biến để có được giá trị này là sử dụng sizeof(arr). Vấn đề: giống như với cái trước; nếu bạn có một con trỏ thay vì một mảng, chương trình của bạn sẽ hoạt động.

Giải pháp cho vấn đề liên quan đến việc sử dụng cùng một macro như trước đây, mà chúng ta biết là an toàn (nó phá vỡ quá trình biên dịch nếu nó được áp dụng cho một con trỏ):

#define ARRAY_BYTES(arr)        (sizeof((arr)[0]) * ARRAY_SIZE(arr))

Cách thức hoạt động rất đơn giản: nó hoàn tác phân chia ARRAY_SIZE, vì vậy sau khi hủy bỏ toán học, bạn kết thúc chỉ với một sizeof(arr), nhưng với sự an toàn bổ sung của ARRAY_SIZEcông trình.

Ví dụ về cách sử dụng:

void foo(ptrdiff_t nmemb)
{
        int arr[nmemb];

        memset(arr, 0, ARRAY_BYTES(arr));
}

memset() cần giá trị này như là đối số thứ ba của nó.

Như trước đây, nếu mảng được nhận dưới dạng tham số (con trỏ), nó sẽ không biên dịch và chúng ta sẽ phải thay thế lệnh gọi macro bằng giá trị:

void foo(ptrdiff_t nmemb, int arr[nmemb])
{

        memset(arr, 0, sizeof(arr[0]) * nmemb);
}

Cập nhật (23 / apr / 2020): -Wsizeof-pointer-divlà lỗi :

Hôm nay tôi phát hiện ra rằng cảnh báo mới trong GCC chỉ hoạt động nếu macro được xác định trong tiêu đề không phải là tiêu đề hệ thống. Nếu bạn xác định macro trong một tiêu đề được cài đặt trong hệ thống của bạn (thường là /usr/local/include/hoặc /usr/include/) ( #include <foo.h>), trình biên dịch sẽ KHÔNG phát ra cảnh báo (Tôi đã thử GCC 9.3.0).

Vì vậy, chúng tôi có #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))và muốn làm cho nó an toàn. Chúng tôi sẽ cần C11 _Static_assert()và một số tiện ích mở rộng GCC: Tuyên bố và Tuyên bố trong Biểu thức , __builtin_types_compiverse_p :

#define is_same_type(a, b)      __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(a)             (!is_same_type((a), &(a)[0]))
#define Static_assert_array(a)  _Static_assert(is_array(a), "Not a `[]` !")

#define ARRAY_SIZE(arr)         (                                       \
{                                                                       \
        Static_assert_array(arr);                                       \
        sizeof(arr) / sizeof((arr)[0]);                                \
}                                                                       \
)

Bây giờ ARRAY_SIZE()là hoàn toàn an toàn, và do đó tất cả các dẫn xuất của nó sẽ an toàn.


Cập nhật: libbsd cung cấp __arraycount():

Libbsd cung cấp macro __arraycount()trong <sys/cdefs.h>, không an toàn vì nó thiếu một cặp dấu ngoặc đơn, nhưng chúng ta có thể tự thêm các dấu ngoặc đơn đó và do đó chúng ta thậm chí không cần phải viết phân chia trong tiêu đề của mình (tại sao chúng ta sẽ sao chép mã đã tồn tại? ). Macro đó được xác định trong tiêu đề hệ thống, vì vậy nếu chúng ta sử dụng nó, chúng ta buộc phải sử dụng các macro ở trên.

#include <sys/cdefs.h>


#define is_same_type(a, b)      __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(a)             (!is_same_type((a), &(a)[0]))
#define Static_assert_array(a)  _Static_assert(is_array(a), "Not a `[]` !")

#define ARRAY_SIZE(arr)         (                                       \
{                                                                       \
        Static_assert_array(arr);                                       \
        __arraycount((arr));                                            \
}                                                                       \
)

#define ARRAY_SSIZE(arr)        ((ptrdiff_t)ARRAY_SIZE(arr))
#define ARRAY_BYTES(arr)        (sizeof((arr)[0]) * ARRAY_SIZE(arr))

Một số hệ thống cung cấp nitems()trong <sys/param.h>thay vào đó, và một số hệ thống cung cấp cả hai. Bạn nên kiểm tra hệ thống của mình và sử dụng hệ thống bạn có và có thể sử dụng một số điều kiện tiền xử lý cho tính di động và hỗ trợ cả hai.


Cập nhật: Cho phép macro được sử dụng ở phạm vi tệp:

Thật không may, ({})phần mở rộng gcc không thể được sử dụng ở phạm vi tệp. Để có thể sử dụng macro ở phạm vi tệp, xác nhận tĩnh phải ở bên trong sizeof(struct {}). Sau đó, nhân nó 0với để không ảnh hưởng đến kết quả. Một diễn viên (int)có thể là tốt để mô phỏng một hàm trả về (int)0(trong trường hợp này là không cần thiết, nhưng sau đó nó có thể tái sử dụng cho những thứ khác).

#include <sys/cdefs.h>


#define is_same_type(a, b)      __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(a)             (!is_same_type((a), &(a)[0]))
#define Static_assert_array(a)  _Static_assert(is_array(a), "Not a `[]` !")
#define must_be_array(a)        (                                       \
        0 * (int)sizeof(                                                \
                struct {                                                \
                        Static_assert_array(a);                         \
                        char ISO_C_forbids_a_struct_with_no_members__;  \
                }                                                       \
        )                                                               \
)

#define ARRAY_SIZE(arr)         (__arraycount((arr)) + must_be_array(arr))
#define ARRAY_SSIZE(arr)        ((ptrdiff_t)ARRAY_SIZE(arr))
#define ARRAY_BYTES(arr)        (sizeof((arr)[0]) * ARRAY_SIZE(arr))

3
Bạn có phiền để giải thích tại sao các downvote? Điều này cho thấy một giải pháp cho một công trình không an toàn và phổ biến ( sizeof(arr)) không được hiển thị ở nơi khác : ARRAY_BYTES(arr).
Cacahuete Frito

2
ARRAY_SIZE đủ phổ biến để được sử dụng một cách tự do và ARRAY_BYTES rất rõ ràng trong tên của nó, nên được xác định bên cạnh ARRAY_SIZE để người dùng có thể nhìn thấy dễ dàng và bằng cách sử dụng nó, tôi không nghĩ bất kỳ ai đọc mã đều nghi ngờ về điều gì. nó làm Ý tôi là không sử dụng đơn giản sizeof, mà sử dụng công trình này thay thế; nếu bạn cảm thấy muốn viết các công trình này mỗi lần, bạn có thể sẽ mắc lỗi (rất phổ biến nếu bạn sao chép dán và cũng rất phổ biến nếu bạn viết chúng mỗi lần vì chúng có nhiều dấu ngoặc đơn) ...
Cacahuete Frito

3
..., Vì vậy, tôi đứng ở kết luận chính: một đơn sizeofrõ ràng là không an toàn (lý do là trong câu trả lời) và không sử dụng macro nhưng sử dụng các công trình tôi cung cấp, mỗi lần, thậm chí còn không an toàn hơn, vì vậy cách duy nhất để đi là macro.
Cacahuete Frito

3
@MarkHarrison Tôi biết sự khác biệt giữa con trỏ và mảng. Nhưng đã có lần tôi có một hàm mà sau đó tôi đã tái cấu trúc thành các hàm nhỏ và đầu tiên là một mảng, sau đó là một con trỏ và đó là một điểm mà nếu bạn quên thay đổi kích thước, bạn vặn nó và không dễ thấy một trong số đó
Cacahuete Frito

3
@hyde Ngoài ra tôi biết sự khác biệt không có nghĩa là mọi người đều biết sự khác biệt và tại sao không sử dụng thứ gì đó về cơ bản loại bỏ 100% các lỗi đó? Lỗi đó gần như đã biến nó thành Linux; nó đã đến Linus, điều đó có nghĩa là nó đã vượt qua rất nhiều sự xem xét kỹ lưỡng và điều đó cũng có nghĩa là có khả năng chính lỗi đó đã biến nó thành Linux trong một phần khác của kernel, như ông nói.
Cacahuete Frito

11

"bạn đã giới thiệu một cách tinh tế để tự bắn vào chân mình"

Mảng C 'bản địa' không lưu trữ kích thước của chúng. Do đó, nên lưu chiều dài của mảng trong một biến / const riêng biệt và truyền nó bất cứ khi nào bạn vượt qua mảng, đó là:

#define MY_ARRAY_LENGTH   15
int myArray[MY_ARRAY_LENGTH];

Bạn NÊN luôn luôn tránh các mảng bản địa (trừ khi bạn không thể, trong trường hợp đó, chú ý đến bàn chân của bạn). Nếu bạn đang viết C ++, hãy sử dụng bộ chứa 'vectơ' của STL . "So với các mảng, chúng cung cấp hiệu năng gần như giống nhau" và chúng hữu ích hơn nhiều!

// vector is a template, the <int> means it is a vector of ints
vector<int> numbers;  

// push_back() puts a new value at the end (or back) of the vector
for (int i = 0; i < 10; i++)
    numbers.push_back(i);

// Determine the size of the array
cout << numbers.size();

Xem: http://www.cplusplus.com/reference/stl/vector/


Tôi đã đọc rằng cách thích hợp để khai báo hằng số nguyên trong C là sử dụng enumkhai báo.
Raffi Khatchadourian

Câu hỏi là về C, không phải C ++. Vì vậy, không có STL.
Cacahuete Frito

11
#define SIZE_OF_ARRAY(_array) (sizeof(_array) / sizeof(_array[0]))

6
Lưu ý rằng điều này chỉ hoạt động cho các mảng thực tế, không phải con trỏ xảy ra để trỏ đến mảng.
David Schwartz

5

Nếu bạn thực sự muốn làm điều này để vượt qua mảng của mình, tôi khuyên bạn nên thực hiện một cấu trúc để lưu trữ một con trỏ đến loại bạn muốn một mảng và một số nguyên biểu thị kích thước của mảng. Sau đó, bạn có thể chuyển điều đó xung quanh chức năng của bạn. Chỉ cần gán giá trị biến mảng (con trỏ cho phần tử đầu tiên) cho con trỏ đó. Sau đó, bạn có thể đi Array.arr[i]lấy phần tử thứ i và sử dụng Array.sizeđể lấy số phần tử trong mảng.

Tôi bao gồm một số mã cho bạn. Nó không hữu ích lắm nhưng bạn có thể mở rộng nó với nhiều tính năng hơn. Thành thật mà nói, nếu đây là những điều bạn muốn, bạn nên ngừng sử dụng C và sử dụng ngôn ngữ khác với các tính năng này được tích hợp.

/* Absolutely no one should use this...
   By the time you're done implementing it you'll wish you just passed around
   an array and size to your functions */
/* This is a static implementation. You can get a dynamic implementation and 
   cut out the array in main by using the stdlib memory allocation methods,
   but it will work much slower since it will store your array on the heap */

#include <stdio.h>
#include <string.h>
/*
#include "MyTypeArray.h"
*/
/* MyTypeArray.h 
#ifndef MYTYPE_ARRAY
#define MYTYPE_ARRAY
*/
typedef struct MyType
{
   int age;
   char name[20];
} MyType;
typedef struct MyTypeArray
{
   int size;
   MyType *arr;
} MyTypeArray;

MyType new_MyType(int age, char *name);
MyTypeArray newMyTypeArray(int size, MyType *first);
/*
#endif
End MyTypeArray.h */

/* MyTypeArray.c */
MyType new_MyType(int age, char *name)
{
   MyType d;
   d.age = age;
   strcpy(d.name, name);
   return d;
}

MyTypeArray new_MyTypeArray(int size, MyType *first)
{
   MyTypeArray d;
   d.size = size;
   d.arr = first;
   return d;
}
/* End MyTypeArray.c */


void print_MyType_names(MyTypeArray d)
{
   int i;
   for (i = 0; i < d.size; i++)
   {
      printf("Name: %s, Age: %d\n", d.arr[i].name, d.arr[i].age);
   }
}

int main()
{
   /* First create an array on the stack to store our elements in.
      Note we could create an empty array with a size instead and
      set the elements later. */
   MyType arr[] = {new_MyType(10, "Sam"), new_MyType(3, "Baxter")};
   /* Now create a "MyTypeArray" which will use the array we just
      created internally. Really it will just store the value of the pointer
      "arr". Here we are manually setting the size. You can use the sizeof
      trick here instead if you're sure it will work with your compiler. */
   MyTypeArray array = new_MyTypeArray(2, arr);
   /* MyTypeArray array = new_MyTypeArray(sizeof(arr)/sizeof(arr[0]), arr); */
   print_MyType_names(array);
   return 0;
}

1
Không thể mã upvote mà strcpy(d.name, name);không xử lý tràn.
chux - Phục hồi Monica

4

Cách tốt nhất là bạn lưu thông tin này, ví dụ, trong một cấu trúc:

typedef struct {
     int *array;
     int elements;
} list_s;

Thực hiện tất cả các chức năng cần thiết như tạo, hủy, kiểm tra sự bình đẳng và mọi thứ khác bạn cần. Nó là dễ dàng hơn để vượt qua như là một tham số.


6
Bất kỳ lý do int elementsvs size_t elements?
chux - Phục hồi Monica

3

Hàm sizeoftrả về số byte được sử dụng bởi mảng của bạn trong bộ nhớ. Nếu bạn muốn tính số lượng phần tử trong mảng của mình, bạn nên chia số đó với sizeofloại biến của mảng. Giả sử int array[10];, nếu số nguyên loại biến trong máy tính của bạn là 32 bit (hoặc 4 byte), để có được kích thước của mảng, bạn nên làm như sau:

int array[10];
int sizeOfArray = sizeof(array)/sizeof(int);

2

Bạn có thể sử dụng &toán tử. Đây là mã nguồn:

#include<stdio.h>
#include<stdlib.h>
int main(){

    int a[10];

    int *p; 

    printf("%p\n", (void *)a); 
    printf("%p\n", (void *)(&a+1));
    printf("---- diff----\n");
    printf("%zu\n", sizeof(a[0]));
    printf("The size of array a is %zu\n", ((char *)(&a+1)-(char *)a)/(sizeof(a[0])));


    return 0;
};

Đây là đầu ra mẫu

1549216672
1549216712
---- diff----
4
The size of array a is 10

7
Tôi đã không downvote, nhưng điều này giống như đánh một cây đinh bằng gạch vì bạn không nhận thấy một cây búa nằm bên cạnh bạn. Ngoài ra, mọi người có xu hướng nhăn mặt khi sử dụng các biến chưa được khởi tạo ... nhưng ở đây tôi đoán nó phục vụ mục đích của bạn đủ tốt.
Dmitri

2
@Dmitri không có biến chưa được khởi tạo nào được truy cập tại đây
MM

1
Hừm. Con trỏ trừ dẫn đến ptrdiff_t. sizeof()kết quả trong size_t. C không xác định cái nào rộng hơn hoặc cao hơn / cùng thứ hạng. Vì vậy, loại thương số ((char *)(&a+1)-(char *)a)/(sizeof(a[0]))không chắc chắn size_tvà do đó in với zcó thể dẫn đến UB. Đơn giản chỉ cần sử dụng printf("The size of array a is %zu\n", sizeof a/sizeof a[0]);là đủ.
chux - Phục hồi Monica

1
(char *)(&a+1)-(char *)akhông phải là hằng số và có thể được tính vào thời gian chạy, ngay cả với kích thước cố định a[10]. sizeof(a)/sizeof(a[0])là hằng số được thực hiện tại thời gian biên dịch trong trường hợp này.
chux - Phục hồi Monica

1

Câu trả lời đơn giản nhất:

#include <stdio.h>

int main(void) {

    int a[] = {2,3,4,5,4,5,6,78,9,91,435,4,5,76,7,34};//for Example only
    int size;

    size = sizeof(a)/sizeof(a[0]);//Method

    printf ("size = %d",size);
    return 0;
}

1

Một giải pháp thanh lịch hơn sẽ là

size_t size = sizeof(a) / sizeof(*a);

0

Bên cạnh các câu trả lời đã được cung cấp, tôi muốn chỉ ra một trường hợp đặc biệt bằng cách sử dụng

sizeof(a) / sizeof (a[0])

Nếu alà một mảng của char, unsigned charhoặc signed charbạn không cần sử dụng sizeofhai lần vì một sizeofbiểu thức với một toán hạng của các loại này luôn luôn dẫn đến 1.

Trích dẫn từ C18,6.5.3.4 / 4:

" Khi sizeofđược áp dụng cho một toán hạng có kiểu char, unsigned charhoặc signed char, (hoặc một phiên bản chất lượng của chúng) kết quả là 1."

Do đó, sizeof(a) / sizeof (a[0])sẽ tương đương với NUMBER OF ARRAY ELEMENTS / 1nếu alà một mảng kiểu char, unsigned charhoặc signed char. Sự phân chia qua 1 là dư thừa.

Trong trường hợp này, bạn chỉ cần viết tắt và làm:

sizeof(a)

Ví dụ:

char a[10];
size_t length = sizeof(a);

Nếu bạn muốn một bằng chứng, đây là một liên kết đến GodBolt .


Tuy nhiên, bộ phận duy trì sự an toàn, nếu loại thay đổi đáng kể (mặc dù những trường hợp này rất hiếm).


1
Bạn có thể vẫn thích áp dụng macro với phân chia, vì loại có thể thay đổi trong tương lai (mặc dù có thể không xảy ra) và phân chia được biết đến vào thời gian biên dịch, vì vậy trình biên dịch sẽ tối ưu hóa nó đi (nếu nó không thay đổi trình biên dịch của bạn).
Cacahuete Frito

1
@CacahueteFrito Vâng, tôi cũng nghĩ về điều đó trong lúc này. Tôi lấy nó làm ghi chú phụ vào câu trả lời. Cảm ơn bạn.
RobertS hỗ trợ Monica Cellio

-1

Lưu ý: Điều này có thể cung cấp cho bạn hành vi không xác định như được chỉ ra bởi MM trong bình luận.

int a[10];
int size = (*(&a+1)-a) ;

Để biết thêm chi tiết xem tại đây và cũng ở đây .


2
Đây là hành vi không xác định về mặt kỹ thuật; các *nhà điều hành có thể không được áp dụng cho một con trỏ qua-the-end
MM

3
"hành vi không xác định" có nghĩa là Tiêu chuẩn C không xác định hành vi. Nếu bạn thử nó trong chương trình của bạn thì bất cứ điều gì cũng có thể xảy ra
MM

@MM bạn đang nói *(&a+1) - a;khác với (&a)[1] - a;ở trên, không cả hai *(&a+1)(&a)[1]được tính là 1 quá khứ?
QuentinUK

@QuentinUK hai biểu thức của bạn đều giống nhau, x[y]được định nghĩa là*(x + (y))
MM

@MM tôi cũng nghĩ vậy. Nhưng câu trả lời khác, bởi Arjun Sreedharan, có 38 mũi tên lên và điều này có -1. Và câu trả lời của Arjun Sreedharan không đề cập đến hành vi không xác định.
QuentinUK
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.