2012-06-17 32 views
63

Tôi đang cố gắng tìm hiểu C và tôi hiện đang cố gắng viết cấu trúc dữ liệu ngăn xếp cơ bản, nhưng tôi dường như không thể nhận được quyền cơ bản malloc/free.Bộ nhớ miễn phí được cấp phát trong một chức năng khác?

Dưới đây là đoạn code tôi đã sử dụng (tôi chỉ đăng một phần nhỏ vào đây để minh họa cho một vấn đề cụ thể, chứ không phải tổng mã, nhưng được thông báo lỗi được tạo ra chỉ bằng cách chạy mã ví dụ này trong valgrind)

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

typedef struct Entry { 
    struct Entry *previous; 
    int value; 
} Entry; 

void destroyEntry(Entry entry); 

int main(int argc, char *argv[]) 
{ 
    Entry* apple; 
    apple = malloc(sizeof(Entry)); 
    destroyEntry(*(apple)); 
    return 0; 
} 

void destroyEntry(Entry entry) 
{ 
    Entry *entry_ptr = &entry; 
    free(entry_ptr); 
    return; 
} 

Khi tôi chạy nó thông qua valgrind với --leak-check=full --track-origins=yes, tôi nhận được lỗi sau:

==20674== Invalid free()/delete/delete[]/realloc() 
==20674== at 0x4028E58: free (vg_replace_malloc.c:427) 
==20674== by 0x80485B2: destroyEntry (testing.c:53) 
==20674== by 0x8048477: main (testing.c:26) 
==20674== Address 0xbecc0070 is on thread 1's stack 

tôi nghĩ rằng lỗi này có nghĩa là chức năng destroyEntry không được phép sửa đổi al bộ nhớ đặt rõ ràng trong chính. Có đúng không? Tại sao tôi không thể chỉ cần free bộ nhớ mà tôi đã phân bổ trong main trong một chức năng khác? (và là hành vi này bằng cách nào đó cụ thể cho chính?)

+17

+1 cho câu hỏi rõ ràng và SSCCE. –

+7

@MatteoItalia Tôi chưa bao giờ nghe nói về [SSCCE] (http://sscce.org/) trước đây. Chắc chắn là một khái niệm tốt. Cảm ơn đã giới thiệu tôi với nó. –

+0

Gọi theo giá trị ??? –

Trả lời

50

Bất cứ khi nào bạn chuyển một tham số cho một hàm, một bản sao được tạo và hàm hoạt động trên bản sao đó. Vì vậy, trong trường hợp của bạn, bạn đang cố gắng để free một bản sao của đối tượng ban đầu, mà không có ý nghĩa gì.

Bạn nên sửa đổi chức năng của mình để lấy con trỏ và sau đó bạn có thể gọi nó trực tiếp trên con trỏ đó.

36

Điều này được truyền theo giá trị, có nghĩa là bản sao được tạo, do đó bạn cố gắng giải phóng bộ nhớ, trong đó biến cục bộ entry nằm. Lưu ý rằng entry là một đối tượng có thời lượng lưu trữ tự động và bộ nhớ mà nó cư trú sẽ được giải phóng tự động khi chương trình của bạn vượt quá phạm vi chức năng destroyEntry.

void destroyEntry(Entry entry) 
{ 
    Entry *entry_ptr = &entry; 
    free(entry_ptr); 
    return; 
} 

Chức năng của bạn nên lấy một con trỏ (đi ngang qua tham khảo):

void destroyEntry(Entry *entry) 
{ 
    free(entry); 
} 

Sau đó, thay vì destroyEntry(*(apple)); bạn chỉ cần gọi destroyEntry(apple);. Lưu ý rằng nếu không có chức năng nào khác được kết nối với chức năng destroyEntry, thì điều đó là thừa và tốt hơn là chỉ cần gọi trực tiếp free(apple).

9

Các câu trả lời khác ở đây chỉ ra vấn đề chính - bởi vì bạn dereference táo của bạn khi bạn gọi destroyEntry trong main(), nó đi qua tham chiếu, tạo một bản sao.

Ngay cả khi bạn biết vấn đề của mình, nó giúp quay trở lại lỗi và cố gắng kết nối văn bản của những gì bạn đang gặp phải vấn đề, để lần sau nó xuất hiện, bạn có nhiều khả năng nó ra nhanh chóng. Tôi thấy lỗi C và C++ đôi khi có vẻ mơ hồ.

Nói chung, khi tôi gặp sự cố khi giải phóng con trỏ hoặc xóa đối tượng, tôi muốn in địa chỉ, đặc biệt là khi tôi phân bổ địa chỉ và ngay khi tôi cố gắng giải phóng nó. valgrind đã cho bạn địa chỉ của con trỏ xấu, nhưng nó giúp so sánh nó với một con trỏ tốt.

int main() 
{ 
    Entry * apple; 
    apple = malloc(sizeof(Entry)); 
    printf("apple's address = %p", apple); // Prints the address of 'apple' 
    free(apple); // You know this will work 
} 

Sau khi thực hiện điều đó, bạn sẽ nhận thấy sự printf() tuyên bố đã cho bạn một cái gì đó như địa chỉ 0x8024712 (chỉ chiếm một địa chỉ trong phạm vi tổng quát phải), nhưng sản lượng valgrind bạn đã 0x4028E58. Bạn sẽ nhận thấy chúng ở hai nơi rất khác nhau (trên thực tế, "0x4 ..."là trên ngăn xếp, không phải là đống nơi malloc() phân bổ từ, nhưng tôi giả sử nếu bạn chỉ mới bắt đầu không phải là cờ đỏ cho bạn), vì vậy bạn biết bạn đang cố gắng giải phóng bộ nhớ khỏi địa chỉ sai, do đó "không hợp lệ miễn phí()"

Vì vậy, từ đó bạn có thể nói với chính mình "Được rồi, bằng cách nào đó con trỏ của tôi bị hỏng." Bạn đã đun sôi vấn đề của mình thành một ví dụ nhỏ, dễ hiểu Bạn sẽ mất nhiều thời gian để giải quyết vấn đề này từ đó

TL; DR - khi gặp lỗi liên quan đến con trỏ, hãy thử in địa chỉ hoặc tìm chúng trong trình gỡ rối yêu thích của bạn. .

Không ai trong số này là để khuyến khích đăng câu hỏi của bạn trên Stack Exchange, tất nhiên. Hàng trăm lập trình viên có thể sẽ được hưởng lợi từ việc bạn đã làm như vậy.

+0

Cảm ơn bạn đã đăng bài này ... đó là một điểm thực sự tốt đẹp về việc tìm ra lỗi với con trỏ và như vậy. Tôi đã được học về stack so với heap, nhưng không nhận ra rằng họ đã có địa chỉ bộ nhớ tổng quát. (như vậy, 'NULL' ->' 0x0', 'stack' ->' 0x4' và heap là gì khác?). –

+0

Ngoài ra, tôi không thể thay đổi một ký tự cho bài đăng của bạn, nhưng trong chuỗi printf: '" địa chỉ của apple =% d "' chuỗi định dạng thực sự phải là '% p', không phải'% d', phải không? –

+0

Tôi không thể cho bạn biết chắc chắn rằng địa chỉ heap sẽ luôn là '0x8 ...' và địa chỉ ngăn xếp sẽ luôn là '0x4 ...'. Thành thật mà nói, tôi chủ yếu là lập trình nhúng những ngày này, nơi không gian địa chỉ được xác định rất tốt và quá trình của bạn là một trong những chỉ chạy. Tuy nhiên, sự hiểu biết của tôi là trên x86, mỗi quá trình nhận được không gian địa chỉ ảo của riêng nó và theo kinh nghiệm của tôi, những "quy tắc" đó dường như đúng. Cảm ơn bạn đã chỉ ra vấn đề% d. Nó hoạt động (trong đó nó in địa chỉ), nhưng nó khó đọc hơn. Tôi thường sử dụng "0x% x" bản thân mình vì đó là cách tôi thường thấy nó. – gkimsey

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