2012-05-13 7 views
48

Hôm nay tôi đã giúp một người bạn của tôi với một số mã C, và tôi đã tìm thấy một số hành vi kỳ lạ mà tôi không thể giải thích tại sao nó xảy ra. Chúng tôi đã có tệp TSV với một danh sách các số nguyên, với một int mỗi dòng. Dòng đầu tiên là số dòng mà danh sách có.Sự khác biệt giữa loại mảng và mảng được phân bổ với malloc

Chúng tôi cũng có tệp c với "readfile" rất đơn giản. Dòng đầu tiên được đọc thành n, số dòng, sau đó có một khởi tạo là:

int list[n] 

và cuối cùng là vòng lặp for với fscanf.

Đối với các n nhỏ (đến ~ 100.000), mọi thứ đều ổn. Tuy nhiên, chúng tôi thấy rằng khi n lớn (10^6), một sự phân đoạn sẽ xảy ra.

Cuối cùng, chúng tôi đã thay đổi khởi tạo danh sách công việc

int *list = malloc(n*sizeof(int)) 

và tất cả mọi thứ khi tốt, ngay cả với n rất lớn.

Ai đó có thể giải thích tại sao điều này xảy ra? những gì đã gây ra segfault với int danh sách [n], đã được dừng lại khi chúng tôi bắt đầu sử dụng list = malloc (n * sizeof (int))?

Trả lời

109

Có một số phần khác nhau tại đây.

Đầu tiên là sự khác biệt giữa việc khai báo một mảng như

int array[n]; 

int* array = malloc(n * sizeof(int)); 

Trong phiên bản đầu tiên, bạn đang khai báo một đối tượng với thời gian lưu trữ tự động. Điều này có nghĩa là mảng chỉ tồn tại miễn là hàm gọi nó tồn tại. Trong phiên bản thứ hai, bạn đang nhận được bộ nhớ với thời lượng lưu trữ động, có nghĩa là nó sẽ tồn tại cho đến khi nó được phân phối rõ ràng với free.

Lý do mà phiên bản thứ hai hoạt động ở đây là chi tiết triển khai cách C thường được biên dịch. Thông thường, bộ nhớ C được chia thành nhiều vùng, bao gồm cả ngăn xếp (cho các cuộc gọi hàm và biến cục bộ) và vùng heap (đối với các đối tượng ed malloc). Ngăn xếp thường có kích thước nhỏ hơn nhiều so với đống; thường là 8MB. Do vậy, nếu bạn cố gắng bố trí một mảng lớn với

int array[n]; 

Sau đó, bạn có thể vượt quá không gian lưu trữ của chồng, gây ra segfault. Mặt khác, heap thường có kích thước rất lớn (ví dụ như không gian trống trên hệ thống), và do đó, malloc ing một đối tượng lớn sẽ không gây ra lỗi ngoài bộ nhớ.

Nói chung, hãy cẩn thận với các mảng có độ dài thay đổi trong C. Chúng có thể dễ dàng vượt quá kích thước ngăn xếp. Ưu tiên malloc trừ khi bạn biết kích thước nhỏ hoặc bạn thực sự chỉ muốn mảng trong một khoảng thời gian ngắn.

Hy vọng điều này sẽ hữu ích!

+1

Câu trả lời rất rõ ràng ... cảm ơn bạn! –

+1

Câu trả lời hay! Tôi đã tự hỏi nếu đó cũng là một sự khác biệt về tốc độ? –

+1

Do ảnh hưởng của địa phương tham chiếu, tôi nghi ngờ mảng được phân bổ theo stack sẽ nhanh hơn để truy cập và bản thân 'malloc' chậm hơn nhiều so với việc chỉ chạm vào con trỏ ngăn xếp. Nhưng thực sự, tốt nhất là sử dụng bất kỳ phương pháp nào phù hợp hơn cho nhiệm vụ trong tầm tay. – templatetypedef

2

danh sách int [n] lưu trữ dữ liệu trong ngăn xếp, trong khi malloc lưu trữ dữ liệu trong vùng lưu trữ.

Ngăn xếp có giới hạn và không có nhiều không gian, trong khi vùng heap lớn hơn nhiều.

1

int list[n] là VLA, phân bổ trên ngăn xếp thay vì trên heap. Bạn không cần phải giải phóng nó (nó tự động giải phóng ở cuối cuộc gọi hàm) và nó phân bổ nhanh chóng nhưng không gian lưu trữ rất hạn chế, như bạn đã phát hiện ra. Bạn phải phân bổ các giá trị lớn hơn trên heap.

1

khai này phân bổ bộ nhớ trên stack

int list[n] 

malloc cấp phát trên heap.

Kích thước ngăn xếp thường nhỏ hơn heap, vì vậy nếu bạn phân bổ quá nhiều bộ nhớ trên ngăn xếp, bạn sẽ nhận được luồng ngăn xếp.

Xem thêm this answer for further information

0

Khi bạn phân bổ sử dụng một malloc, bộ nhớ được phân bổ từ đống và không phải từ chồng, mà là hạn chế hơn nhiều về kích thước.

8
int list[n] 

Phân bổ không gian cho n số nguyên trên stack, mà thường là khá nhỏ. Sử dụng bộ nhớ trên ngăn xếp nhanh hơn nhiều so với giải pháp thay thế, nhưng nó khá nhỏ và dễ dàng tràn ngăn xếp (tức là phân bổ quá nhiều bộ nhớ) nếu bạn làm những việc như phân bổ mảng lớn hoặc đệ quy quá sâu. Bạn không phải tự deallocate bộ nhớ được phân bổ theo cách này, nó được thực hiện bởi trình biên dịch khi mảng đi ra khỏi phạm vi.

malloc mặt khác phân bổ không gian trong đống, mà thường là rất lớn so với chồng.Bạn sẽ phải phân bổ một lượng bộ nhớ lớn hơn nhiều trên heap để xả nó, nhưng chậm hơn rất nhiều để cấp phát bộ nhớ trên heap hơn là trên stack, và bạn phải deallocate nó bằng tay qua free khi bạn sử dụng xong nó.

+1

"Sử dụng bộ nhớ trên ngăn xếp nhanh hơn nhiều so với giải pháp thay thế", ý bạn là "phân bổ" hoặc "truy cập" ở đây? AFAIK, phân bổ stack nhanh hơn nhiều nhưng nó cũng có đúng cho việc truy cập (đọc/ghi) không? Cảm ơn – dragonxlwang

1

Giả sử bạn có một cài đặt điển hình trong việc thực hiện của bạn có nhiều khả năng nhất mà:

int list[n] 

phân bổ danh sách trên ngăn xếp của bạn, nơi như:

int *list = malloc(n*sizeof(int)) 

bộ nhớ phân bổ trên đống của bạn.

Trong trường hợp ngăn xếp thường có giới hạn về mức độ lớn của chúng có thể phát triển (nếu chúng có thể phát triển). Trong trường hợp của một đống vẫn còn một giới hạn, nhưng có xu hướng được phần lớn và (rộng rãi) hạn chế bởi RAM của bạn + không gian địa chỉ trao đổi + mà thường ít nhất là một thứ tự của cường độ lớn hơn, nếu không nhiều hơn.

0

Nếu bạn đang sử dụng Linux, bạn có thể đặt ulimit -s thành giá trị lớn hơn và điều này cũng có thể làm việc để phân bổ ngăn xếp. Khi bạn cấp phát bộ nhớ trên ngăn xếp, bộ nhớ đó vẫn còn cho đến khi kết thúc quá trình thực thi chức năng của bạn. Nếu bạn cấp phát bộ nhớ trên heap (sử dụng malloc), bạn có thể giải phóng bộ nhớ bất kỳ lúc nào bạn muốn (ngay cả trước khi kết thúc việc thực thi hàm của bạn).

Nói chung, heap nên được sử dụng cho phân bổ bộ nhớ lớn.

0
int array[n]; 

Đây là ví dụ về mảng được phân bổ tĩnh và tại thời gian biên dịch, kích thước của mảng sẽ được biết. Và mảng sẽ được cấp phát trên ngăn xếp.

int *array(malloc(sizeof(int)*n); 

Đây là ví dụ về mảng được phân bổ động và kích thước của mảng sẽ được người dùng biết đến trong thời gian chạy. Và mảng sẽ được cấp phát trên heap.