2010-02-01 35 views
7

Tôi đã hỏi question earlier về việc xác định cấu trúc bằng cách sử dụng malloc. Đây là câu trả lời tôi đã được đưa ra bởi phần lớn:Xác định cấu trúc trong C với Malloc

struct retValue* st = malloc(sizeof(*st)); 

Tôi đã thấy một người bạn mã của tôi, và chúng tôi đã đến một trở ngại. Có thể ai đó vui lòng giải thích lý do mã này hoạt động không? Từ quan điểm của tôi, * st chưa được xác định khi bạn malloc nó, vì vậy có thể có bất kỳ loại rác trong đó. Nó phải là malloc(sizeof(struct retValue))

Nhờ sự giúp đỡ

+0

Bạn đã trả lời câu hỏi của riêng mình. sizeof (struct retValue) là chính xác –

+1

Xin lỗi, câu hỏi không phải là 'Điều này đúng hay chính xác là gì?' đó là 'Tại sao nó hoạt động?' – Blackbinary

+2

Nó có thể giúp hiểu nếu bạn thay đổi thuật ngữ. Bạn không * định nghĩa * một cấu trúc bằng cách sử dụng malloc, bạn đang * phân bổ * một cấu trúc bằng cách sử dụng malloc. Bạn đang định nghĩa 'st', là một con trỏ (không phải là một cấu trúc). Con trỏ đó đã được định nghĩa và có sẵn để sử dụng trong biểu thức khởi tạo (trên RHS của dấu bằng), nó chỉ không có giá trị, vì vậy hầu hết các sử dụng sẽ không hợp lệ. Điều này là OK, tuy nhiên, vì sizeof không sử dụng giá trị. –

Trả lời

19

Sizeof xem loại biểu thức được cung cấp cho nó, nó không đánh giá biểu thức. Vì vậy, bạn chỉ cần đảm bảo rằng các biến được sử dụng trong biểu thức được khai báo để trình biên dịch có thể suy ra loại của chúng.

Trong ví dụ của bạn, st đã được khai báo là pointer-to-struct-retValue. Do đó trình biên dịch có thể suy ra loại biểu thức "* st".

Mặc dù nó không giống như nó đã được khai báo trong mã của bạn, trình biên dịch đã xử lý nó cho bạn. Tất cả các khai báo trong mã của bạn được chuyển đến đầu khối mà chúng xuất hiện bởi trình biên dịch. Giả sử bạn viết

Một cách để minh họa kiến ​​thức có sẵn cho trình biên dịch là xem xét đầu ra trung gian mà nó tạo ra. Hãy xem xét ví dụ mã này ...

struct retValue {long int a, long int b}; 
... 
printf("Hello World!\n"); 
struct retValue* st = malloc(sizeof(*st)); 

Sử dụng gcc là một ví dụ và teh mã trên trong main() chức năng của test.c, chúng ta hãy nhìn vào sản lượng trung gian bằng cách chạy ...

gcc -fdump-tree-cfg test.c 

Trình biên dịch sẽ tạo ra các tập tin test.c.022t.cfg - Hãy nhìn vào nó và bạn sẽ thấy

[ ... removed internal stuff ...] 
;; Function main (main) 

Merging blocks 2 and 3 
main (argc, argv) 
{ 
    struct retValue * st; 
    int D.3097; 
    void * D.3096; 

    # BLOCK 2 
    # PRED: ENTRY (fallthru) 
    __builtin_puts (&"Hello World!"[0]); 
    D.3096 = malloc (16); 
    st = (struct retValue *) D.3096; 
    D.3097 = 0; 
    return D.3097; 
    # SUCC: EXIT 

} 

Lưu ý cách khai báo được di chuyển đến đầu khối và đối số cho malloc đã được thay thế bằng giá trị thực tế biểu thị kích thước của loại biểu thức được đánh giá. Như đã chỉ ra trong các bình luận, thực tế là việc khai báo đã được chuyển lên đầu khối là một chi tiết thực hiện của trình biên dịch. Tuy nhiên, thực tế là trình biên dịch có thể làm điều này và cũng để chèn kích thước chính xác vào malloc tất cả cho thấy rằng trình biên dịch đã có thể suy ra các thông tin cần thiết từ đầu vào.

Cá nhân tôi thích đặt tên loại thực tế làm tham số cho sizeof, nhưng đó có thể là câu hỏi về kiểu mã hóa, nơi tôi muốn nói rằng tính nhất quán cao hơn sở thích cá nhân.

+1

Câu hỏi của ông là về '* st' làm" những điều xấu ". '* st' không hợp lệ nếu' st' không trỏ đến dữ liệu hợp lệ. Nhưng vì 'sizeof' không đánh giá đối số của nó, nên sử dụng' * st' làm toán hạng cho 'sizeof', ngay cả khi' st' là 'NULL'. –

+0

Trên thực tế, câu hỏi là "làm thế nào/tại sao nó hoạt động mặc dù thực tế là st không thể được dereferenced tại thời điểm malloc cần phải biết bao nhiêu không gian để phân bổ" ... – VoidPointer

+1

Đưa một tuyên bố vào đầu của khối trong phân tích cú pháp phiên bản mã là chi tiết triển khai. Trình biên dịch là không cần thiết để làm điều đó, mặc dù bạn xảy ra, và mã của người hỏi làm việc cho dù trình biên dịch có làm được hay không. Đoạn mã sau không biên dịch, cho thấy rằng việc khai báo không thực sự được di chuyển: 'sizeof (* st); char * st = 0; '. –

19

Các sizeof điều hành không thực sự đánh giá toán hạng của nó - nó chỉ nhìn vào kiểu của nó. Điều này được thực hiện tại thời gian biên dịch thay vì thời gian chạy. Vì vậy, nó có thể được thực hiện một cách an toàn trước khi biến được gán.

+0

Tôi nghĩ tôi hiểu. Vì vậy, câu nói của bạn 'sizeof' nhìn * st và nói 'Oh thats một con trỏ!' và sau đó phân bổ đủ bộ nhớ cho một con trỏ. Nó không quan tâm những gì * st thực sự đang nắm giữ. Đúng? – Blackbinary

+5

Không, đó là cách khác. '* st' có nghĩa là" điều mà điểm trỏ tới ", do đó trình biên dịch trả về kích thước của cấu trúc, không phải kích thước của con trỏ. –

+3

@Blackbinary: Close: 'st' là một con trỏ, nhưng' * st' là một cấu trúc. Vì vậy, nó nhìn vào '* st' và nói" Oh đó là một 'struct retValue'!" và sau đó phân bổ đủ bộ nhớ cho cấu trúc retValue. Nội dung thực tế của '* st' không quan trọng. – interjay

1

Điều quan trọng là tuyên bố/định nghĩa của loại cấu trúc chứ không phải định nghĩa của đối tượng của một lớp như vậy. Khi bạn đạt đến malloc, một trình khai báo/định nghĩa sẽ được trình biên dịch gặp phải, bạn sẽ gặp lỗi trình biên dịch khác.

Thực tế là sizeof không đánh giá toán hạng của nó là vấn đề phụ.

Một nit nhỏ: hãy nhớ rằng chúng tôi cần ngoặc khi chúng tôi cung cấp tên loại để sizeof như trong:

sizeof(struct retValue); 

và không có trong trường hợp của các đối tượng, chúng ta chỉ cần làm:

sizeof *st; 

Xem tiêu chuẩn:

6.5 .3 các nhà khai thác unary Cú pháp

unary-expression: 
[...] 
sizeof unary-expression 
sizeof (type-name) 
0

Trong C, sizeof là một nhà điều hành, và không đánh giá đối số của nó. Điều đó có thể dẫn đến các hiệu ứng "thú vị", rằng một người mới đến C không nhất thiết phải dự đoán. Tôi đã đề cập chi tiết hơn trong câu hỏi my answer đối với câu hỏi "Ngôn ngữ lạ nhất".

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