2010-03-11 22 views
12

Tôi đã xem qua một số mã di sản có chứa một hàm như thế này:Mã không phải là mã kế thừa này có trả về mảng char cục bộ sai?

LPCTSTR returnString() 
{ 
    char buffer[50000]; 
    LPCTSTR t; 

    /*Some code here that copies some string into buffer*/ 

    t = buffer; 
    return t; 
} 

Bây giờ, tôi rất nghi ngờ rằng điều này là sai. Tôi đã thử gọi hàm và nó trả về chuỗi mà bạn mong đợi nó trả về. Tuy nhiên, tôi không thực sự thấy điều đó xảy ra như thế nào: không phải là mảng char phải được lưu trữ trên ngăn xếp, và do đó deallocated sau khi thoát khỏi chức năng? Nếu tôi sai, và nó được lưu trữ trên heap, không phải là chức năng này tạo ra một rò rỉ bộ nhớ?

+5

Vâng, bạn là chính xác. Bạn không nên trả về một mảng tĩnh từ một hàm. – AraK

+1

@nvl Ngay cả khi 't' đang được cấp phát động, nó sẽ yêu cầu toán tử gán quá tải sao cho đúng bộ đệm. – KevenK

+2

Không phải rò rỉ bộ nhớ, chỉ "may mắn là nó hoạt động". Nếu bộ nhớ mà mảng được cấp phát trên ngăn xếp được tái sử dụng, thì bạn sẽ có rác trong mảng của mình. Đó là một cách hay để tạo các lỗi tối nghĩa. –

Trả lời

8

Mã của bạn đang trưng bày hành vi không xác định - trong trường hợp này, UB là nó có vẻ "hoạt động". Nếu bạn muốn mảng được lưu trữ trên heap, bạn cần phải phân bổ nó với new []. Người gọi hàm này sau đó sẽ chịu trách nhiệm xóa nó thông qua con trỏ hàm trả về.

+3

Tốt hơn, trả về một 'std :: basic_string ' hoặc đối tượng tương tự biết cách sao chép và giải quyết bất kỳ tài nguyên nào mà nó sở hữu. –

3

Cảm giác của bạn là chính xác; mã rất sai.

Bộ nhớ thực sự nằm trên ngăn xếp và biến mất với chức năng kết thúc.

Nếu bộ đệm là static thì ít nhất bạn cũng có thể mong đợi nó hoạt động cho một cuộc gọi tại một thời điểm (trong một ứng dụng đơn luồng).

5

Bạn chính xác, điều này không được đảm bảo để hoạt động; và trên thực tế, nếu bạn thực sự lưu trữ chuỗi ký tự 50.000 trong bộ đệm này, hãy gọi một số hàm (gọi hàm, gọi hàm ..) sau này, trong hầu hết mọi hệ thống, chuỗi này sẽ bị hỏng, do chức năng stack-frame được đẩy lên stack.

Lý do duy nhất nó hoạt động là bộ nhớ ngăn xếp cũ (trên hầu hết các hệ thống) không bị xóa sau khi hàm trả về.

[Chỉnh sửa] Để làm rõ, nó có vẻ hoạt động vì ngăn xếp không có cơ hội tăng 50.000 byte. Hãy thử đổi nó thành char buffer[10]; và gọi một số chức năng sau returnString() và bạn sẽ thấy nó bị hỏng.

1

mã này trả về một con trỏ tới bộ nhớ được cấp phát trên ngăn xếp. nó rất nguy hiểm bởi vì nếu bạn cố gắng chuyển con trỏ này đến một hàm khác, bộ nhớ sẽ bị ghi đè bởi lệnh gọi hàm thứ hai.

thay vì điều này, bạn có thể sử dụng một bộ đệm tĩnh:

static char buffer[50000]; 

mà không được cấp phát trên stack do đó, một con trỏ đến nó vẫn có giá trị. (đây không phải là chủ đề an toàn, rõ ràng).

6

Không có rò rỉ bộ nhớ, nhưng chức năng vẫn sai - buffer thực sự được tạo trên ngăn xếp và thực tế người gọi có thể đọc nó chỉ là may mắn (chỉ có thể truy cập ngay sau khi gọi hàm returnString(). Bộ đệm đó có thể bị ghi đè bởi bất kỳ cuộc gọi chức năng nào khác hoặc thao tác ngăn xếp khác.

Cách thích hợp để truyền dữ liệu lên chuỗi cuộc gọi là cung cấp bộ đệm và kích thước thành hàm để điền.

1

Bạn nói đúng, mã không đúng. :)

Bạn nên nghiên cứu phân bổ bộ nhớ trong C/C++. Dữ liệu có thể nằm trong hai khu vực: stack và heap.Các biến cục bộ được lưu trữ trong ngăn xếp. malloc ed và new dữ liệu ed được lưu trữ trong heap. Trạng thái của ngăn xếp là cục bộ cho hàm - các biến nằm trong khung ngăn xếp - vùng chứa sẽ được giải phóng khi hàm trả về. Vì vậy, con trỏ trở nên bị hỏng.

Heap là toàn cầu, vì vậy, tất cả dữ liệu được lưu trữ ở đó cho đến khi rõ ràng delet ed hoặc free d theo lập trình viên. Bạn có thể dựa vào khu vực đó.

1

Trong khi mọi người đều nghĩ rằng hành vi không xác định, và trong trường hợp này có vẻ đúng, điều quan trọng trong trường hợp như thế này là xem xét các khả năng khác.

Ví dụ: quá tải operator=(const char*) có thể nằm sau cảnh phân bổ bộ nhớ yêu cầu. Mặc dù đây không phải là trường hợp (theo hiểu biết tốt nhất của tôi) với typedef của Microsoft, nhưng điều quan trọng cần lưu ý là trong trường hợp như thế này.

Trong trường hợp này, tuy nhiên, dường như chỉ thuận tiện khi nó hoạt động và chắc chắn đó không phải là hành vi được đảm bảo. Như những người khác đã lưu ý, điều này thực sự nên được cố định.

+2

Điều này là không thể - chức năng trả về một char * (trong vỏ bọc của một LPCSTR), và bạn không thể quá tải operator =() cho con trỏ. –

+1

Tôi biết rằng * trong trường hợp này * đây không phải là những gì đang xảy ra. Vấn đề là trong các tình huống như thế này (đặc biệt là với các bài tập đối tượng hoặc các lớp container), nó là một tính năng quan trọng cần lưu ý khi đánh giá những gì đang xảy ra. Nếu không, những gì có vẻ như một nhiệm vụ xấu có thể trong thực tế là hoàn toàn hợp lệ. Quan điểm của tôi hoàn toàn hướng đến cách kiểm tra một tình huống như thế này, và không có ý định mô tả những gì đang xảy ra * trong trường hợp cụ thể này *. – KevenK

2

Có rất ít sự khác biệt giữa một mảng và một con trỏ trong C/C++. Vì vậy, tuyên bố:

 
t = buffer; 

Thực tế hoạt động vì "bộ đệm" nghĩa là địa chỉ của mảng. Địa chỉ không được lưu trữ rõ ràng trong bộ nhớ mặc dù cho đến khi bạn đặt nó vào t (tức là bộ đệm không phải là một con trỏ). buffer [n] và t [n] sẽ tham chiếu cùng một phần tử của mảng. Mảng của bạn được cấp phát trên ngăn xếp, do đó bộ nhớ được giải phóng - không bị xóa - khi hàm trả về. Nếu bạn nhìn vào nó trước khi nó bị ghi đè bởi cái gì khác thì nó sẽ xuất hiện tốt.

1

Đây là một lỗi nguy hiểm ẩn trong mã của bạn. Trong C và C++ bạn không được phép trả về một con trỏ để ngăn xếp dữ liệu trong một hàm. Nó dẫn đến hành vi không xác định. Tôi sẽ giải thích tại sao.

Chương trình C/C++ hoạt động bằng cách đẩy dữ liệu vào và tắt ngăn xếp chương trình. Khi bạn gọi một hàm, tất cả các tham số được đẩy lên ngăn xếp, và sau đó tất cả các biến cục bộ được đẩy lên ngăn xếp. Khi chương trình thực hiện nó có thể đẩy và bật thêm nhiều mục vào và tắt ngăn xếp trong chức năng của bạn. Trong ví dụ của bạn, bộ đệm được đẩy lên ngăn xếp và sau đó t được đẩy lên ngăn xếp . Ngăn xếp có thể trông như thế nào,

  • stack Previous
  • thông số
  • (dữ liệu khác)
  • đệm (50000 bytes)
  • t (con trỏ sizeof)

Tại thời điểm này , t nằm trên ngăn xếp và trỏ tới bộ đệm, cũng nằm trên ngăn xếp. Khi hàm trả về, thời gian chạy bật tất cả các biến trên ngăn xếp, trong đó bao gồm t, bộ đệm và các tham số. Trong trường hợp của bạn, bạn trả về con trỏ t, do đó tạo một bản sao của nó trong chức năng gọi.

Nếu chức năng gọi sau đó nhìn vào những gì t trỏ đến, nó sẽ tìm thấy rằng nó trỏ đến bộ nhớ trên ngăn xếp có thể có hoặc không tồn tại.(Thời gian chạy popped nó ra khỏi ngăn xếp, nhưng các dữ liệu trên stack vẫn có thể ở đó do trùng hợp ngẫu nhiên, có thể không).

Tin vui là, nó không phải là vô vọng. Có các công cụ tự động có thể tìm kiếm các loại lỗi này trong phần mềm của bạn và báo cáo chúng. Chúng được gọi là công cụ phân tích tĩnh . Sentry là một ví dụ như một chương trình có thể báo cáo loại lỗi này là .

1

Tôi đồng ý rằng hầu hết các tính năng tương tự của một biến tự động, tuy nhiên, tôi phản hồi KevenK rằng điều này không được đảm bảo nếu nó là C++ như thẻ chỉ định. Chúng tôi không biết LPCTSTR là gì. Điều gì nếu một tiêu đề bao gồm chứa một cái gì đó như thế này:

(vâng tôi biết rò rỉ này, không phải là điểm)


class LPCTSTR{ 
private: 
    char * foo; 

public: 
    LPCTSTR & operator=(char * in){ 
    foo = strdup(in); 
    } 

    const char * getFoo(){ 
    return foo; 
    } 


}; 

+0

Nếu đúng, mã vẫn không hoạt động. Bạn sẽ cần một hàm tạo bản sao do người dùng định nghĩa để sao chép giá trị LPCSTR ra khỏi hàm một cách chính xác. –

+1

Điểm không phải là cách ví dụ cụ thể này quản lý tuổi thọ của bộ nhớ. Nó là để cho thấy rằng bạn không thể chỉ đơn giản là giả định rằng LPCTSTR là một vĩ mô cho char * như hầu hết các câu trả lời upvoted có. Có các triển khai hợp lệ hoàn hảo của một lớp LPCTSTR làm cho hàm này hoạt động theo một cách được xác định rõ. Nếu đây là C, chắc chắn rồi. Tuy nhiên, câu hỏi được gắn thẻ C++. – frankc

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