2010-02-23 26 views
9

Tôi biết điều này sẽ không làm việc vì các biến x bị phá hủy khi trở về chức năng:trở lại dữ liệu khai báo trong hàm

int* myFunction() 
{ 
    int x = 4; return &x; 
} 

vậy làm thế nào để tôi một cách chính xác trả về một con trỏ đến một cái gì đó tôi có thể tạo bên trong hàm và tôi phải chăm sóc gì? Làm cách nào để tránh rò rỉ bộ nhớ? malloc

Tôi cũng đã sử dụng:

int* myFunction2() 
{ 
    int* x = (int*)malloc(sizeof int); *x = 4; return x; 
} 

Làm thế nào để bạn làm một cách chính xác này - trong C và C++?

+3

Bạn có quyền, câu trả lời là 'myFunction2()' bạn chỉ cần nhớ để giải phóng bộ nhớ của bạn sau này. Đó là vấn đề không có bộ thu gom rác – tzenes

+4

Một lần nữa, trong khi C và C++ chia sẻ nhiều tính năng, có nhiều câu hỏi với các câu trả lời hoàn toàn khác nhau tùy thuộc vào ngôn ngữ. Làm thế nào để tránh rò rỉ bộ nhớ là một trong số chúng, vì nó là cách tạo ra một thứ gì đó bên trong một hàm ... Bạn thực sự quan tâm đến ngôn ngữ nào? –

Trả lời

6

Cách tiếp cận thứ hai của bạn là chính xác. Bạn chỉ cần ghi rõ ràng rằng người gọi "sở hữu" con trỏ kết quả và chịu trách nhiệm giải phóng nó.

Do sự phức tạp này, hiếm khi làm điều này cho các loại "nhỏ" như int, mặc dù tôi giả sử bạn chỉ sử dụng một int ở đây vì một ví dụ.

Một số người cũng sẽ thích đưa con trỏ đến đối tượng đã được phân bổ dưới dạng tham số hơn là phân bổ đối tượng trong nội bộ. Điều này làm cho nó rõ ràng hơn rằng người gọi có trách nhiệm deallocating đối tượng (kể từ khi họ phân bổ nó ở nơi đầu tiên), nhưng làm cho trang web gọi một chút chi tiết hơn, vì vậy nó là một thương mại-off.

5

Đối với C++, trong nhiều trường hợp, chỉ cần trả về theo giá trị. Ngay cả trong trường hợp đối tượng lớn hơn, RVO sẽ thường xuyên tránh sao chép không cần thiết.

1

Trong C++, bạn nên sử dụng new:

int *myFunction() 
{ 
    int blah = 4; 
    return new int(blah); 
}

Và để thoát khỏi nó, sử dụng xóa:

int main(void) 
{ 
    int *myInt = myFunction(); 
    // do stuff 
    delete myInt; 
}

Lưu ý rằng tôi gọi constructor sao chép cho int trong khi sử dụng new , sao cho giá trị "4" được sao chép vào bộ nhớ heap. Cách duy nhất để có được một con trỏ đến một cái gì đó trên stack đáng tin cậy là để sao chép nó vào heap bằng cách gọi new đúng cách.

EDIT: Như đã lưu ý trong câu trả lời khác, bạn cũng sẽ cần phải ghi lại rằng con trỏ cần được giải phóng bởi người gọi sau này. Nếu không, bạn có thể bị rò rỉ bộ nhớ.

2

Phương pháp C++ để tránh rò rỉ bộ nhớ. (Ít nhất là khi Bạn bỏ qua sản lượng chức năng)

std::auto_ptr<int> myFunction() { 
    std::auto_ptr<int> result(new int(4)); 
    return result; 
} 

Sau đó gọi nó là:

std::auto_ptr<int> myFunctionResult = myFunction(); 

EDIT: Như đã chỉ ra bởi Joel. std :: auto_ptr có nhược điểm riêng của nó và nói chung nên tránh. Thay vào đó std :: auto_ptr Bạn có thể sử dụng boost :: shared_ptr (std :: tr1 :: shared_ptr).

boost::shared_ptr<int> myFunction() { 
    boost::shared_ptr<int> result(new int(5)); 
    return result; 
} 

hoặc khi sử dụng C++ 0x phù hợp với trình biên dịch Bạn có thể sử dụng std :: unique_ptr.

std::tr1::unique_ptr<int> myFunction() { 
    std::tr1::unique_ptr<int> result(new int(5)); 
    return result; 
} 

Sự khác biệt chính là:

  • shared_ptr cho phép nhiều trường hợp của shared_ptr trỏ đến con trỏ RAW cùng. Nó sử dụng cơ chế đếm tham chiếu để đảm bảo rằng bộ nhớ sẽ không được giải phóng miễn là ít nhất một cá thể của shared_ptr tồn tại.

  • unique_ptr chỉ cho phép một phiên bản giữ con trỏ nhưng có ngữ nghĩa di chuyển thực sự không giống như auto_ptr.

+0

auto_ptr thường bị cau mày bởi vì nó có ngữ nghĩa kì lạ. Khi bạn chuyển kết quả đó đến một hàm, bạn không còn sở hữu nó nữa và không thể truy cập nó nữa. – Joel

+0

Bạn nói đúng và tôi đồng ý hoàn toàn. Đó là lý do tại sao tôi đã viết (ít nhất là khi bạn bỏ qua đầu ra chức năng). Giải pháp tốt hơn là tăng ví dụ :: shared_ptr nhưng tôi đề xuất giải pháp hiện tại đã được giải quyết. Và có Tôi biết về std :: tr1 :: shared_ptr – lollinus

+0

@Joel: Nếu bạn chuyển nó đến một hàm khác, hàm đó có thể sẽ không dùng auto_ptr. Vì vậy, bạn sẽ sử dụng 'f (myFunctionResult.get())' và vẫn giữ quyền sở hữu. Nếu chức năng thực hiện auto_ptr, rõ ràng là quyền sở hữu được chuyển. –

7

Đối với C++, bạn có thể sử dụng con trỏ thông minh để thực thi chuyển quyền sở hữu. auto_ptr hoặc boost::shared_ptr là các tùy chọn tốt.

3

Một khả năng được đi qua các chức năng một con trỏ:

void computeFoo(int *dest) { 
    *dest = 4; 
} 

này là tốt đẹp bởi vì bạn có thể sử dụng một chức năng như vậy với một biến tự động:

int foo; 
computeFoo(&foo); 

Với phương pháp này bạn cũng giữ cho bộ nhớ quản lý trong cùng một phần của mã, tức là. bạn không thể bỏ lỡ một malloc chỉ vì nó xảy ra ở đâu đó bên trong một hàm:

// Compare this: 
int *foo = malloc(…); 
computeFoo(foo); 
free(foo); 

// With the following: 
int *foo = computeFoo(); 
free(foo); 

Trong trường hợp thứ hai, bạn sẽ dễ dàng quên miễn phí khi bạn không thấy malloc. Điều này thường ít nhất được giải quyết một phần theo quy ước, ví dụ: “Nếu tên hàm bắt đầu bằng XY, điều đó có nghĩa là bạn sở hữu dữ liệu mà nó trả về.”

Trường hợp góc quay trở lại thú vị cho biến "chức năng" đang khai báo biến tĩnh:

int* computeFoo() { 
    static int foo = 4; 
    return &foo; 
} 

Tất nhiên đây là điều ác đối với chương trình bình thường, nhưng có thể hữu ích vào một ngày nào đó.

+0

tĩnh là tốt; thật đáng buồn, nó không phải là = malloc – xealits

1

Có một cách tiếp cận khác - kê khai x tĩnh. Trong trường hợp này nó sẽ được đặt trong phân đoạn dữ liệu, không phải trên ngăn xếp, do đó nó có sẵn (và liên tục) trong thời gian chạy chương trình.

int *myFunction(void) 
{ 
    static int x = 4; 
    return &x; 
} 

Xin lưu ý rằng việc chuyển nhượng x=4 sẽ được thực hiện chỉ theo yêu cầu đầu tiên của myFunction:

int *foo = myFunction(); // foo is 4 
*foo = 10;     // foo is 10 
*foo = myFunction();  // foo is 10 

NB! Sử dụng các biến tĩnh phạm vi chức năng không phải là kỹ thuật an toàn trên tread.

+0

Tại sao lại là downvote? Câu trả lời của tôi là hoàn toàn chính xác. – qrdl

0

Con trỏ chia sẻ TR1 hoặc TR1 thường là cách để đi. Nó tránh được chi phí bản sao và cho phép bạn xóa tự động. Vì vậy, chức năng của bạn sẽ trông giống như:

boost::shared_ptr<int> myFunction2() 
{ 
    boost::shared_ptr<int> x = new int; 

    *x = 4; 
    return x; 
} 

Tùy chọn khác chỉ cho phép sao chép. Đó không phải là quá xấu nếu đối tượng là nhỏ (như thế này) hoặc bạn có thể sắp xếp để tạo ra các đối tượng trong báo cáo trở lại. Trình biên dịch thường sẽ tối ưu hóa một bản sao nếu đối tượng được tạo trong câu lệnh trả về.

0

tôi sẽ cố gắng một cái gì đó như thế này:

int myFunction2b(int * px) 
{ 
    if(px) 
    { 
    *px = 4; 
    return 1; 
    } 

    // Choice 1: Assert or Report Error 
    // Choice 2: Allocate memory for x. Caller has to be written accordingly. 

    // My choice is 1 
    assert(0 && "Argument is NULL pointer"); 
    return 0; 

} 
-1

Bạn đang hỏi làm thế nào để trở lại một cách chính xác một con trỏ. Đó là câu hỏi sai, bởi vì những gì bạn nên làm là sử dụng con trỏ thông minh hơn là con trỏ thô. scoped_ptr và shared_ptr (có sẵn trong thúc đẩy và TR1) là con trỏ tốt để xem xét (ví dụ herehere)

Nếu bạn cần con trỏ liệu cho một cái gì đó (ví dụ như đi đến một chức năng C), các get() phương pháp sẽ cung cấp nó.

Nếu bạn phải tạo con trỏ thô, ví dụ: cho bài tập về nhà, sau đó bạn có thể sử dụng malloc() (như bạn đã làm) hoặc mới trong một hàm, và hy vọng bạn nhớ để de-phân bổ bộ nhớ (thông qua miễn phí()xóa tương ứng) Hoặc trong thành ngữ hơi ít có khả năng bị rò rỉ, bạn có thể tạo con trỏ với mới, chuyển nó tới hàm và hủy phân bổ với xóa khi bạn đã hoàn tất. Một lần nữa, mặc dù, sử dụng con trỏ thông minh.

1

Đoạn mã thứ hai của bạn là chính xác.

Để tránh rò rỉ bộ nhớ, tôi để các quy ước mã hóa giúp tôi.

xxxCreate() sẽ cấp phát bộ nhớ cho xxx và khởi tạo bộ nhớ. xxxDelete() sẽ hủy/hỏng xxx và giải phóng nó.

xxxInit() sẽ khởi tạo xxx (không bao giờ phân bổ) xxxDestroy() sẽ tiêu diệt/xxx tham nhũng (không bao giờ miễn phí)

Thêm vào đó, tôi cố gắng thêm mã để xóa/hủy/miễn phí ngay sau khi tôi thêm mã để tạo/init/malloc. Nó không hoàn hảo, nhưng tôi thấy rằng nó giúp tôi phân biệt giữa các vật phẩm cần được giải thoát và những thứ không có, cũng như giảm khả năng tôi sẽ quên để giải phóng một cái gì đó sau này.

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