2009-08-06 36 views
21

Đi đoạn mã sau:Tại sao bạn chỉ định kích thước khi sử dụng malloc trong C?

int *p = malloc(2 * sizeof *p); 

p[0] = 10; //Using the two spaces I 
p[1] = 20; //allocated with malloc before. 

p[2] = 30; //Using another space that I didn't allocate for. 

printf("%d", *(p+1)); //Correctly prints 20 
printf("%d", *(p+2)); //Also, correctly prints 30 
         //although I didn't allocate space for it 

Với dòng malloc(2 * sizeof *p) Tôi đang phân bổ không gian cho hai số nguyên, phải không? Nhưng nếu tôi thêm int vào vị trí thứ ba, tôi vẫn được phân bổ chính xác và có thể truy xuất được.

Vì vậy, câu hỏi của tôi là, tại sao bạn chỉ định kích thước khi bạn sử dụng malloc?

+9

Bạn dường như có p chọn [3] vì nó lớn hơn 2 và do đó "ra khỏi phạm vi." Bạn nói đúng, nhưng hãy nhớ rằng bạn phải suy nghĩ theo cách được lập chỉ mục 0, vì vậy trên thực tế, ngay cả p [2] cũng nằm ngoài phạm vi. Nếu bạn đã cấp không gian cho hai int, bạn có thể lấy int bằng cách sử dụng * p (hoặc p [0]) và * (p + 1) (hoặc p [1]), không p [1] và p [2] . – MatrixFrog

+0

Bạn đang ở đó, sai lầm ngu ngốc từ phía tôi. Tôi sẽ chỉnh sửa câu hỏi như không nhầm lẫn ý định ban đầu của nó –

+2

Trên đầu trang của tất cả các câu trả lời điểm tại vấn đề, bạn đã có một segfault đảm bảo trên tay của bạn nếu malloc trả về NULL, mà nó được phép làm. Luôn luôn kiểm tra giá trị trả về của malloc trước khi bạn sử dụng nó. Nếu đó là NULL, trình quản lý bộ nhớ từ chối cấp phát cho bạn nhiều bộ nhớ hơn (thường vì bạn đã yêu cầu quá nhiều hoặc bạn đã sử dụng hết tất cả nó sẵn sàng cung cấp cho bạn). –

Trả lời

126

Logic đơn giản: Nếu bạn không đậu xe trong một chỗ đậu xe hợp pháp, không có gì xảy ra nhưng thỉnh thoảng xe của bạn có thể bị kéo và bạn có thể bị kẹt với một khoản tiền phạt lớn.Và, đôi khi, khi bạn cố gắng tìm đường đến pound nơi chiếc xe của bạn bị kéo, bạn có thể bị một chiếc xe tải chạy qua.

malloc cung cấp cho bạn nhiều chỗ đỗ xe hợp pháp như bạn đã hỏi. Bạn có thể cố gắng đỗ xe ở nơi khác, có vẻ như nó hoạt động, nhưng đôi khi nó sẽ không hoạt động.

Đối với các câu hỏi như vậy, Memory Allocation section of the C FAQ là tham chiếu hữu ích để tham khảo ý kiến. Xem 7.3b.

Lưu ý liên quan (hài hước), xem thêm danh sách bloopers bởi ART.

+32

+ 1 Tôi thích sự tương tự này –

+3

Ẩn dụ hoàn hảo. Bạn có thể đỗ bất hợp pháp ngày này qua ngày khác và có thể không có gì xấu xảy ra. Nhưng vẫn có khả năng bị kéo hoặc bị đánh cắp, vì vậy bạn không nên làm điều đó. – MatrixFrog

+0

Cảm ơn tất cả mọi người vì những người upvotes. Tôi đánh giá cao nó. –

0

Khi bạn sử dụng * (p + 3), bạn đang giải quyết ngoài giới hạn ngay cả khi sử dụng 2 * sizeof (* p), do đó bạn đang truy cập khối bộ nhớ không hợp lệ, hoàn hảo cho lỗi seg.

Bạn chỉ định kích cỡ b/c nếu không, hàm sẽ không biết khối lượng lớn của khối ra khỏi bộ nhớ heap để phân bổ cho chương trình của bạn cho con trỏ đó.

+0

* p không phải là một con trỏ, đó là int, p là một con trỏ. – horseyguy

+2

Đó là sai, sizeof (* p) là kích thước của loại được trỏ đến bởi p. Tôi thích sizeof (* p) tốt hơn sizeof (int), bởi vì sau này làm cho nó nhiều lỗi dễ bị thay đổi loại. –

+0

Bạn đúng về những nhận xét đầu tiên của tôi. Tôi đã thay đổi nó. – stanigator

31

C vui lòng để bạn tự chụp mình trong đầu. Bạn vừa mới sử dụng bộ nhớ ngẫu nhiên trên heap. Với những hậu quả không lường trước được.

Tuyên bố từ chối: Chương trình C thực sự cuối cùng của tôi đã được thực hiện khoảng 15 năm trước.

+2

Không cần từ chối trách nhiệm, câu trả lời này hoàn toàn chính xác. Thông thường biến tiếp theo mà bạn phân bổ là biến được ghi đè, nhưng nếu bạn thực sự không may mắn, bạn có thể nhấn vào không gian biến của chương trình khác và vặn một thứ ngẫu nhiên. – Ricket

+0

Bởi vì tôi có nghĩa là, nếu sau khi khai báo p bạn cũng tuyên bố int * q = malloc (sizeof (int)); (một mảng với một phần tử), có khả năng (nhưng không được bảo đảm) rằng p [2] == q [0]. Điều này cũng giới thiệu các trường hợp mà chương trình của bạn có thể tiếp tục, và nó có thể không tàn phá, và sau đó đột nhiên có một trường hợp p [2]! = Q [0] và một lỗi xảy ra một lần ... Những người đến và đi , các lỗi không thể đoán trước là cực kỳ khó gỡ lỗi. – Ricket

4

Thực tế, malloc không phân bổ đủ dung lượng cho số nguyên thứ ba của bạn, nhưng bạn đã "may mắn" và chương trình của bạn không bị lỗi. Bạn chỉ có thể chắc chắn rằng malloc đã phân bổ chính xác những gì bạn yêu cầu, không còn nữa. Nói cách khác, chương trình của bạn đã viết cho một phần bộ nhớ không được cấp phát cho nó.

Vì vậy, malloc cần biết kích thước bộ nhớ bạn cần bởi vì nó không biết bạn sẽ làm gì với bộ nhớ, bao nhiêu đối tượng bạn định viết vào bộ nhớ, v.v ...

+7

Tôi cho rằng điều này thực sự không may mắn :) – bdonlan

+1

Bạn thấy nó không may mắn như thế nào khi mã được chạy trên một số hệ thống nơi mà các hành vi heap khác nhau (nhưng trong tiêu chuẩn của khóa học). "Nó chạy trên máy của tôi" không phải là cụm từ mà khách hàng muốn nghe. – sharptooth

4

Điều này tất cả quay trở lại C cho phép bạn tự bắn mình vào chân. Chỉ vì bạn có thể làm điều này, không có nghĩa là bạn nên làm. Giá trị tại p + 3 chắc chắn không được đảm bảo là những gì bạn đặt ở đó trừ khi bạn phân bổ cụ thể nó bằng cách sử dụng malloc.

13

Bạn nhận được (un) may mắn. Việc truy cập p [3] là không xác định, vì bạn chưa phân bổ bộ nhớ đó cho chính mình. Đọc/ghi phần cuối của một mảng là một trong những cách mà các chương trình C có thể gặp sự cố theo những cách bí ẩn.

Ví dụ: điều này có thể thay đổi một số giá trị trong một số biến khác được phân bổ qua malloc. Điều đó có nghĩa là nó có thể sụp đổ sau đó, và sẽ rất khó để tìm thấy đoạn mã (không liên quan) ghi đè dữ liệu của bạn.

Tệ hơn nữa, bạn có thể ghi đè lên một số dữ liệu khác và có thể không nhận thấy. Hãy tưởng tượng điều này vô tình ghi đè số tiền bạn nợ ai đó ;-)

+8

Nó cũng có khả năng ghi đè thông tin về những gì trong heap, có nghĩa là malloc() và free() có thể làm những điều ngày càng gập ghềnh cho đến khi có một vụ tai nạn rất bí ẩn mà không có nguyên nhân rõ ràng. –

0

Vì malloc() phân bổ trong BYTES. Vì vậy, nếu bạn muốn phân bổ (ví dụ) 2 số nguyên, bạn phải xác định kích thước theo byte của 2 số nguyên. Kích thước của một số nguyên có thể được tìm thấy bằng cách sử dụng sizeof (int) và do đó kích thước theo byte của 2 số nguyên là 2 * sizeof (int). Đặt tất cả những thứ này lại với nhau và bạn nhận được:

int * p = malloc(2 * sizeof(int)); 

Lưu ý: vì ở trên chỉ phân bổ không gian cho HAI số nguyên bạn đang rất nghịch ngợm khi gán thứ 3. Bạn may mắn nó không sụp đổ. :)

+0

thực sự, 'int * p = malloc (2 * sizeof * p);' cũng gán số tiền chính xác vì nó nhân với kích thước của con trỏ, trong trường hợp này, là 'int' –

+0

có, sizeof * p và sizeof (int) trả về cùng một giá trị :) – horseyguy

+0

@Dreas: Đó là lấy 'sizeof' loại được chỉ bởi' p', không phải là 'p'. Phổ biến, cho 'in * p',' sizeof (int) == sizeof (* p) '. – Novelocrat

2

Bộ nhớ được thể hiện dưới dạng một đường kẻ liền kề có thể lưu trữ số. Các chức năng malloc sử dụng một số khe cho thông tin theo dõi của riêng nó, cũng như đôi khi trở lại các khe lớn hơn những gì bạn cần, để khi bạn trả lại cho họ sau đó nó không bị mắc kẹt với một đoạn bộ nhớ nhỏ bất thường. Int thứ ba của bạn hoặc là hạ cánh trên dữ liệu riêng của mallocs, trên không gian trống còn sót lại trong đoạn đã trả về, hoặc trong khu vực bộ nhớ đang chờ xử lý mà malloc đã yêu cầu từ hệ điều hành nhưng chưa được gửi cho bạn.

0

Vì malloc đang phân bổ dung lượng trên heap là một phần của bộ nhớ được chương trình của bạn sử dụng là động được phân bổ. Hệ điều hành bên dưới sau đó cung cấp cho chương trình của bạn số tiền được yêu cầu (hoặc không phải nếu bạn kết thúc với một số lỗi ngụ ý bạn luôn luôn nên kiểm tra trở lại của malloc cho tình trạng lỗi) của bộ nhớ ảo mà nó ánh xạ tới bộ nhớ vật lý (tức là. ma thuật thông minh liên quan đến những thứ phức tạp như phân trang chúng tôi không muốn nghiên cứu kỹ trừ khi chúng tôi viết một hệ điều hành.

0

Như mọi người đã nói, bạn đang ghi vào bộ nhớ không thực sự được phân bổ, có nghĩa là có điều gì đó có thể xảy ra để ghi đè lên dữ liệu của bạn. Để chứng minh sự cố, bạn có thể thử một cái gì đó như thế này:

int *p = malloc(2 * sizeof(int)); 
p[0] = 10; p[1] = 20; p[2] = 30; 
int *q = malloc(2 * sizeof(int)); 
q[0] = 0; // This may or may not get written to p[2], overwriting your 30. 

printf("%d", p[0]); // Correctly prints 10 
printf("%d", p[1]); // Correctly prints 20 
printf("%d", p[2]); // May print 30, or 0, or possibly something else entirely. 

Không có cách nào để đảm bảo chương trình của bạn sẽ phân bổ không gian cho q tại p [2]. Nó có thể trong thực tế chọn một vị trí hoàn toàn khác nhau. Nhưng đối với một chương trình đơn giản như thế này, có vẻ như có khả năng, và nếu nó hiện phân bổ q tại vị trí mà p [2] sẽ là, nó sẽ chứng minh rõ ràng lỗi ngoài phạm vi.

27

Hãy để tôi cung cấp cho bạn sự tương tự về lý do tại sao "hoạt động".

Giả sử bạn cần vẽ một bản vẽ, vì vậy bạn lấy một mẩu giấy, đặt nó phẳng trên bàn của bạn và bắt đầu vẽ.

Thật không may, giấy không đủ lớn, nhưng bạn, không quan tâm, hoặc không nhận thấy, chỉ cần tiếp tục vẽ bản vẽ của bạn.

Khi hoàn tất, bạn lùi lại một bước và nhìn vào bản vẽ của bạn, và nó có vẻ tốt, chính xác như ý bạn, và chính xác như cách bạn vẽ nó.

Cho đến khi ai đó đến và nhặt mảnh giấy của họ mà họ để lại trên bàn trước khi bạn đến.

Bây giờ có một phần bản vẽ bị thiếu. Mảnh bạn vẽ trên giấy của người kia.

Ngoài ra, người đó hiện có các bản vẽ của bạn trên giấy của mình, có thể gây rối với bất cứ điều gì ông muốn có trên giấy thay thế.

Vì vậy, trong khi việc sử dụng bộ nhớ của bạn có vẻ như hoạt động, nó chỉ làm như vậy vì chương trình của bạn kết thúc.Để lại một lỗi như vậy trong một chương trình chạy trong một thời gian và tôi có thể đảm bảo với bạn rằng bạn nhận được kết quả kỳ lạ, sự cố và không có gì.

C được xây dựng giống như cưa xích trên steroid. Có hầu như không có gì bạn không thể làm. Điều này cũng có nghĩa là bạn cần phải biết bạn đang làm gì, nếu không bạn sẽ thấy ngay qua cây và vào chân bạn trước khi bạn biết điều đó.

+4

+1 Một tương tự tuyệt vời –

+0

Đây là một sự tương tự tuyệt vời, vì nó cũng trả lời một cái gì đó của "tại sao không có cái gì bẫy cho điều đó"; người ta có thể xây dựng một khung xung quanh một mảnh giấy để cây bút không thể đi ra ngoài nó, nhưng điều đó chắc chắn sẽ có nhiều công việc hơn là chỉ lấy một mảnh giấy và vẽ một bản vẽ. Trong một số môi trường, chi phí thêm của khung được coi là đáng giá. Trong các môi trường khác, những người có xu hướng được coi là đủ đáng tin cậy mà họ không cần phải bận tâm với khung. – supercat

1

Bạn đang yêu cầu không gian cho hai số nguyên. p [3] giả định rằng bạn có không gian cho 4 số nguyên!

===================

Bạn cần phải nói với malloc bạn cần bao nhiêu vì nó không thể đoán bao nhiêu bộ nhớ mà bạn cần.

malloc có thể làm bất cứ điều gì nó muốn miễn là trả lại ít nhất lượng bộ nhớ bạn yêu cầu.

Giống như yêu cầu chỗ ngồi trong nhà hàng. Bạn có thể được cung cấp một bàn lớn hơn bạn cần. Hoặc bạn có thể được ngồi ở bàn với người khác. Hoặc bạn có thể được cho một cái bàn với một chỗ ngồi. Malloc là miễn phí để làm bất cứ điều gì nó muốn miễn là bạn có được chỗ ngồi duy nhất của bạn.

Là một phần của "hợp đồng" để sử dụng malloc, bạn được yêu cầu không bao giờ tham khảo bộ nhớ ngoài những gì bạn đã yêu cầu vì bạn chỉ được đảm bảo nhận số tiền bạn yêu cầu.

2

Tùy thuộc vào nền tảng, p [500] có thể cũng sẽ "hoạt động".

1

Khi sử dụng malloc(), bạn chấp nhận hợp đồng với thư viện thời gian chạy mà bạn đồng ý yêu cầu nhiều bộ nhớ như bạn dự định sử dụng và đồng ý cung cấp cho bạn. Đó là loại thỏa thuận bắt tay bằng lời nói giữa bạn bè, điều đó thường khiến mọi người gặp rắc rối. Khi bạn truy cập vào một địa chỉ nằm ngoài phạm vi phân bổ của mình, bạn đang vi phạm lời hứa của mình.

Tại thời điểm đó, bạn đã yêu cầu những gì tiêu chuẩn gọi là "Hành vi không xác định" và trình biên dịch và thư viện được phép làm bất cứ điều gì một cách đáp ứng. Thậm chí xuất hiện để hoạt động "chính xác" được cho phép.

Đó là rất không may rằng nó thường hoạt động bình thường, vì lỗi này có thể khó viết các trường hợp thử nghiệm để nắm bắt. Cách tiếp cận tốt nhất để thử nghiệm cho nó liên quan đến việc thay thế malloc() với việc triển khai theo dõi giới hạn kích thước khối và kiểm tra mạnh mẽ vùng heap cho sức khỏe của nó ở mọi cơ hội hoặc sử dụng công cụ như valgrind để xem hành vi của chương trình từ "bên ngoài "và khám phá việc lạm dụng bộ nhớ đệm. Lý tưởng nhất, sự lạm dụng như vậy sẽ thất bại sớm và thất bại lớn tiếng.

Một lý do tại sao việc sử dụng các phần tử gần phân bổ ban đầu thường thành công là người cấp phát thường đưa ra các khối liên quan đến các bội số thuận tiện của việc đảm bảo căn chỉnh và thường dẫn đến một số byte "rảnh" ở cuối phân bổ trước khi bắt đầu tiếp theo. Tuy nhiên, người cấp phát thường lưu trữ thông tin quan trọng mà nó cần để quản lý vùng heap gần các byte đó, vì vậy việc overstepping phân bổ có thể dẫn đến việc hủy bỏ dữ liệu mà bản thân cần phải thực hiện phân bổ thứ hai.

Edit: Các OP khắc phục sự cố bên với *(p+2) xấu hổ chống p[1] vì vậy tôi đã chỉnh sửa câu trả lời của tôi để thả điểm đó.

0

Lý do cho kích thước được đặt cho malloc() là dành cho trình quản lý bộ nhớ để theo dõi lượng không gian đã được đưa ra cho mỗi quy trình trên hệ thống của bạn. Các bảng này giúp hệ thống biết được ai đã phân bổ bao nhiêu không gian và những địa chỉ nào là miễn phí() có thể.

Thứ hai, c cho phép bạn ghi vào bất kỳ phần nào của ram bất cứ lúc nào. Kernel có thể ngăn cản bạn ghi vào một số phần nhất định, gây ra lỗi bảo vệ, nhưng không có gì ngăn cản các lập trình viên cố gắng.

Thứ ba, trong tất cả các khả năng, malloc() lần đầu tiên có thể không chỉ phân bổ 8 byte cho quy trình của bạn. Điều này phụ thuộc vào việc triển khai thực hiện, nhưng có nhiều khả năng người quản lý bộ nhớ sẽ cấp phát một trang đầy đủ cho việc sử dụng của bạn chỉ vì việc phân bổ các khối kích thước trang dễ dàng hơn .... sau đó malloc() tiếp theo sẽ chia nhỏ malloc trước đó () trang ed.

+0

C không cho phép bạn ghi vào bất kỳ phần nào của bộ nhớ. C đảm bảo rằng bạn có thể ghi vào một số phần nhất định và bất kỳ điều gì khác là hành vi không xác định. Điều này có nghĩa là bất cứ điều gì thực hiện là hoàn toàn hợp pháp C, bao gồm định dạng đĩa cứng của bạn, gửi tất cả mọi người trong danh sách liên lạc của bạn tài liệu quảng cáo từ Amway, hoặc thậm chí làm những gì bạn mong đợi. Chỉ cần không đếm vào đó cuối cùng. –

+0

Tôi đoán điểm của tôi là "hành vi không xác định" không được xác định.Nó được xác định bởi việc thực hiện. Có rất nhiều chương trình C ở đó tận dụng lợi thế của thực tế là việc ghi vào các vị trí cụ thể trong bộ nhớ thực hiện điều gì đó cụ thể do việc triển khai cho phép nó. Ví dụ, các thanh ghi mục đích đặc biệt có thể điều khiển một tính năng phần cứng theo cách được định nghĩa ... và chúng tôi hiểu rằng tại thời điểm chúng ta viết mã C. – KFro

4

Hãy thử điều này:

int main (int argc, char *argv[]) { 
    int *p = malloc(2 * sizeof *p); 
    int *q = malloc(sizeof *q); 
    *q = 100; 

    p[0] = 10; p[1] = 20; p[2] = 30; p[3] = 40; 
    p[4] = 50; p[5] = 60; p[6] = 70; 


    printf("%d\n", *q); 

    return 0; 
} 

Trên máy tính của tôi, nó in:

Điều này là do bạn ghi đè lên bộ nhớ phân bổ cho p, và dẫm lên q.

Lưu ý rằng malloc có thể không đặt p và q trong bộ nhớ liền kề do hạn chế căn chỉnh.

-2

Đỗ:

int *p = malloc(2 * sizeof(*p)); // wrong (if type is something greater than a machine word) 

[type] *p = malloc(2 * sizeof([type])); // right. 
+0

'sizeof (* p)': kích thước của điều được trỏ đến bởi 'p' không phải là kích thước của' p'. –

+0

Không, vì kiểu dữ liệu không phải lúc nào cũng là số nguyên. Trong trường hợp này, nó là một con trỏ, nhưng bạn cần phải phân bổ phòng cho kiểu dữ liệu có thể là một kiểu tùy chỉnh, nói một cấu trúc là 40 byte hoặc hơn. Vì vậy, nói cách khác, * p = malloc (2 * sizeof ()) –

+0

Các * trên * p dereferences con trỏ mà làm cho rằng đánh giá để sizeof (int). Nếu p được định nghĩa là double *, bạn sẽ nhận được sizeof (* p) == sizeof (double). –

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