2011-11-06 24 views
71

Có thể nó khác với nền tảng, nhưngTại sao malloc khởi tạo các giá trị bằng 0 trong gcc?

khi tôi biên dịch bằng gcc và chạy mã bên dưới, tôi nhận được 0 mỗi lần trong ubuntu 11.10.

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

int main() 
{ 
    double *a = (double*) malloc(sizeof(double)*100) 
    printf("%f", *a); 
} 

Tại sao malloc hoạt động như thế này mặc dù có calloc?

Điều đó có nghĩa là có chi phí hiệu năng không mong muốn chỉ để khởi tạo các giá trị thành 0 ngay cả khi bạn không muốn nó là đôi khi?


EDIT: Oh, ví dụ trước của tôi không phải là initiazling, nhưng đã xảy ra để sử dụng khối "mới".

Những gì tôi một cách chính xác đang tìm kiếm là lý do tại sao nó khởi nó khi nó phân bổ một khối lớn:

int main() 
{ 
    int *a = (int*) malloc(sizeof(int)*200000); 
    a[10] = 3; 
    printf("%d", *(a+10)); 

    free(a); 

    a = (double*) malloc(sizeof(double)*200000); 
    printf("%d", *(a+10)); 
} 

OUTPUT: 3 
     0 (initialized) 

Nhưng nhờ đã chỉ ra rằng có một lý do an ninh khi mallocing! (Không bao giờ nghĩ về nó). Chắc chắn nó phải khởi tạo bằng không khi phân bổ khối mới, hoặc khối lớn.

+12

Đối với một thử nghiệm thực tế hơn, Bạn đã thử phân bổ, giải phóng và sau đó phân bổ một lần nữa (có thể lặp đi lặp lại mỗi nhiều lần)? Chỉ vì malloc trả về bộ nhớ zero-initialized lần đầu tiên không có nghĩa là bạn có thể tin tưởng vào nó nói chung. – user786653

+1

Nó cũng có thể là bộ nhớ đã được thiết lập 0 bởi hệ điều hành hoặc một cái gì đó và 'malloc' không có gì để làm với nó. –

+0

[Không đưa kết quả của 'malloc' vào C] (http://stackoverflow.com/q/605845/995714) –

Trả lời

157

ngắn Trả lời:

Nó không, nó chỉ xảy ra là zero trong trường hợp của bạn.
(Cũng trường hợp thử nghiệm của bạn không hiển thị dữ kiện là zero Nó chỉ hiển thị nếu một phần tử là zero..)


dài trả lời:

Khi bạn gọi malloc(), một trong hai những điều sẽ xảy ra:

  1. Nó tái chế bộ nhớ được phân bổ trước đó và giải phóng khỏi cùng một quy trình.
  2. Yêu cầu (các) trang mới từ hệ điều hành.

Trong trường hợp đầu tiên, bộ nhớ sẽ chứa dữ liệu còn lại từ phân bổ trước đó. Vì vậy, nó sẽ không bằng không. Đây là trường hợp thông thường khi thực hiện phân bổ nhỏ.

Trong trường hợp thứ hai, bộ nhớ sẽ từ hệ điều hành. Điều này xảy ra khi chương trình hết bộ nhớ - hoặc khi bạn yêu cầu phân bổ rất lớn. (như trường hợp trong ví dụ của bạn)

Đây là bắt: Bộ nhớ đến từ hệ điều hành sẽ được xóa cho bảo mật lý do.*

Khi hệ điều hành cung cấp cho bạn bộ nhớ, nó có thể đã được giải phóng khỏi một quy trình khác. Vì vậy, bộ nhớ đó có thể chứa thông tin nhạy cảm như mật khẩu. Vì vậy, để ngăn chặn bạn đọc dữ liệu như vậy, hệ điều hành sẽ không nó trước khi nó mang lại cho bạn.

* Tôi lưu ý rằng tiêu chuẩn C không nói gì về điều này. Đây hoàn toàn là một hành vi hệ điều hành. Vì vậy, zeroing này có thể hoặc không thể hiện diện trên các hệ thống mà an ninh không phải là một mối quan tâm.


Để cung cấp cho nhiều hơn một nền hiệu suất như thế này:

Như @R. đề cập trong các ý kiến, zeroing này là lý do tại sao bạn nên luôn luôn use calloc() instead of malloc() + memset(). calloc() có thể tận dụng thực tế này để tránh riêng biệt memset().


Mặt khác, lỗi này đôi khi là một nút cổ chai hiệu suất. Trong một số ứng dụng số (chẳng hạn như out-of-place FFT), bạn cần phân bổ một phần lớn bộ nhớ cào. Sử dụng nó để thực hiện bất kỳ thuật toán nào, sau đó giải phóng nó.

Trong những trường hợp này, số không cần thiết là không cần thiết và số tiền trên không thuần túy.

Ví dụ cực đoan nhất mà tôi đã thấy là chi phí zero-zero 20 giây cho hoạt động 70 giây với bộ đệm đệm 48 GB. (Khoảng 30% chi phí.) (Cấp: máy đã thiếu băng thông bộ nhớ.)

Giải pháp rõ ràng là chỉ cần sử dụng lại bộ nhớ theo cách thủ công. Nhưng điều đó thường đòi hỏi phải vượt qua các giao diện đã được thiết lập. (đặc biệt nếu nó là một phần của thói quen thư viện)

+18

+1 vì lý do bảo mật. –

+14

Nhưng bạn * vẫn * không thể đếm trên số đó bằng không trừ khi bạn tự làm như vậy (hoặc với 'calloc', nó sẽ làm gì cho bạn sau khi nhận được bộ nhớ từ hệ điều hành). –

+0

Cảm ơn câu trả lời của bạn. Không bao giờ nghĩ rằng sẽ có một vấn đề an ninh khi mallocing! – SHH

0

Bạn có biết rằng nó chắc chắn đang được khởi tạo không? Có thể là khu vực được trả về bởi malloc() thường xuyên có 0 lúc đầu?

2

Tiêu chuẩn không quy định rằng malloc() nên khởi tạo giá trị bằng 0. Nó chỉ xảy ra ở nền tảng của bạn mà nó có thể được đặt thành 0, hoặc nó có thể bằng không tại thời điểm cụ thể mà bạn đọc giá trị đó.

2

Mã của bạn không chứng minh rằng malloc khởi tạo bộ nhớ của nó đến 0. Điều đó có thể được thực hiện bởi hệ điều hành, trước khi chương trình bắt đầu. Để xem shich là trường hợp, viết một giá trị khác nhau vào bộ nhớ, giải phóng nó, và gọi malloc một lần nữa. Bạn có thể sẽ nhận được cùng một địa chỉ, nhưng bạn sẽ phải kiểm tra điều này. Nếu có, bạn có thể xem để xem nó chứa gì. Hãy cho chúng tôi biết!

20

Hệ điều hành thường sẽ xoá các trang bộ nhớ mới mà nó gửi đến quy trình của bạn để nó không thể xem xét dữ liệu của quá trình cũ hơn. Điều này có nghĩa là lần đầu tiên bạn khởi tạo một biến (hoặc malloc gì đó) nó thường sẽ bằng không, nhưng nếu bạn sử dụng lại bộ nhớ đó (ví dụ như giải phóng nó và lấy một lần nữa) thì tất cả các phiên cược đều bị tắt.

Sự không nhất quán này chính xác là lý do tại sao các biến chưa được khởi tạo lại là một lỗi khó tìm.


Đối với các chi phí hoạt động không mong muốn, tránh hành vi không xác định có lẽ là quan trọng hơn. Bất kể tăng hiệu suất nhỏ nào bạn có thể đạt được trong trường hợp này sẽ không bù đắp được những lỗi bạn sẽ phải xử lý nếu ai đó sửa đổi mã (phá vỡ các giả định trước) hoặc chuyển nó sang hệ thống khác (nơi các giả định có thể không hợp lệ) ngay từ đầu).

+3

+1 ... không chắc chắn nếu "có thể" được yêu cầu trong suy nghĩ chữ in đậm ;-) –

15

Tại sao bạn giả định rằng malloc() khởi tạo về 0?Nó chỉ xảy ra khi cuộc gọi đầu tiên đến số malloc() dẫn đến cuộc gọi đến sbrk hoặc mmap cuộc gọi hệ thống, cấp phát một trang bộ nhớ từ hệ điều hành. Hệ điều hành có nghĩa vụ cung cấp bộ nhớ không khởi tạo vì lý do bảo mật (nếu không, dữ liệu từ các quy trình khác sẽ hiển thị!). Vì vậy, bạn có thể nghĩ rằng có - hệ điều hành lãng phí thời gian zeroing trang. Nhưng không! Trong Linux, có một trang đơn lẻ toàn hệ thống đặc biệt được gọi là 'trang không' và trang đó sẽ được ánh xạ dưới dạng Sao chép-Ghi, nghĩa là chỉ khi bạn thực sự viết trên trang đó, Hệ điều hành sẽ phân bổ một trang khác và khởi tạo nó. Vì vậy, tôi hy vọng điều này sẽ trả lời câu hỏi của bạn về hiệu suất. Mô hình phân trang bộ nhớ cho phép sử dụng bộ nhớ được sắp xếp bằng cách hỗ trợ khả năng lập bản đồ nhiều của cùng một trang cộng với khả năng xử lý trường hợp khi ghi đầu tiên xảy ra.

Nếu bạn gọi free(), phân bổ glibc sẽ trả lại khu vực cho danh sách miễn phí và khi malloc() được gọi lại, bạn có thể nhận được cùng một khu vực nhưng bị bẩn với dữ liệu trước đó. Cuối cùng, free() có thể trả lại bộ nhớ cho hệ điều hành bằng cách gọi lại các cuộc gọi hệ thống.

Chú ý rằng glibcman page trên malloc() Nghiêm nói rằng bộ nhớ không xóa, vì vậy bởi "hợp đồng" trên API, bạn không thể giả định rằng nó không bị xóa. Đây là đoạn trích gốc:

malloc() phân bổ byte kích thước và trả về con trỏ tới bộ nhớ được cấp phát.
Bộ nhớ không bị xóa. Nếu kích thước là 0, thì malloc() trả về giá trị NULL, hoặc một giá trị con trỏ duy nhất mà sau này có thể được chuyển thành miễn phí().

Nếu muốn, bạn có thể đọc thêm về tài liệu đó nếu bạn lo lắng về hiệu suất hoặc các tác dụng phụ khác.

0

Không bao giờ bao giờ đếm trên bất kỳ trình biên dịch nào để tạo mã sẽ khởi tạo bộ nhớ cho bất kỳ thứ gì. malloc đơn giản trả về một con trỏ tới n byte của bộ nhớ một nơi nào đó địa ngục thậm chí nó có thể bị hoán đổi.

Nếu nội dung của bộ nhớ rất quan trọng, hãy tự khởi chạy nó.

+4

Ngoại trừ trường hợp ngôn ngữ đảm bảo rằng nó sẽ được khởi tạo. Các đối tượng tĩnh không khởi tạo rõ ràng được khởi tạo ngầm bằng không. –

2

Từ gnu.org:

khối rất lớn (lớn hơn nhiều so với một trang) được phân bổ với mmap (nặc danh hay qua/dev/zero) bằng cách thực hiện này.

+0

OP là mallocing trong các bước nhỏ mặc dù. Mà tham chiếu mà bạn tìm thấy có bất cứ điều gì về điều đó quá? – hugomg

13

Tôi đã sửa đổi ví dụ của bạn để chứa 2 phân bổ giống hệt nhau. Bây giờ nó rất dễ dàng để xem malloc không khởi tạo bộ nhớ.

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

int main(void) 
{ 
    { 
     double *a = malloc(sizeof(double)*100); 
     *a = 100; 
     printf("%f\n", *a); 
     free(a); 
    } 
    { 
     double *a = malloc(sizeof(double)*100); 
     printf("%f\n", *a); 
     free(a); 
    } 

    return 0; 
} 

Output với gcc 4.3.4

100.000000 
100.000000 
+0

Tôi đã thử những gì bạn đã làm và nếu tôi chỉ phân bổ 100 byte sau đó, mặc dù con trỏ trỏ đến địa chỉ CÙNG, giá trị tại địa chỉ đó là khác nhau. Nếu tôi phân bổ 400 byte hoặc nhiều hơn thì cả giá trị con trỏ và giá trị trong bộ nhớ đều giống nhau. Bạn nghĩ gì có thể là nguyên nhân? –

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