2010-06-27 34 views
5

Tôi có tranh luận với bạn của tôi. Anh ta nói rằng tôi có thể trả về một con trỏ tới dữ liệu cục bộ từ một hàm. Đây không phải là những gì tôi đã học được nhưng tôi không thể tìm thấy một phản đối cho anh ta để chứng minh kiến ​​thức của tôi.Trả về dữ liệu cục bộ từ các hàm trong C và C++ qua con trỏ

đây được minh họa trường hợp:

char *name() { 
    char n[10] = "bodacydo!"; 
    return n; 
} 

Và nó được sử dụng như:

int main() { 
    char *n = name(); 
    printf("%s\n", n); 
} 

Ông nói rằng điều này là hoàn toàn OK, vì sau khi một chương trình gọi tên, nó sẽ trả về một con trỏ đến n, và ngay sau đó nó chỉ in ra. Không có gì khác xảy ra trong chương trình trong khi đó, bởi vì nó đơn luồng và thực hiện là nối tiếp.

Tôi không thể tìm thấy đối số phản đối. Tôi sẽ không bao giờ viết mã như thế, nhưng anh ta cứng đầu và nói điều này hoàn toàn ổn. Nếu tôi là sếp của anh ta, tôi sẽ bắn anh ta để trở thành một thằng ngốc cứng đầu, nhưng tôi không thể tìm thấy một cuộc tranh luận phản đối.

Một ví dụ khác:

int *number() { 
    int n = 5; 
    return &n; 
} 

int main() { 
    int *a = number(); 
    int b = 9; 
    int c = *a * b; 
    printf("%d\n", c); 
} 

tôi sẽ gửi cho anh ta liên kết này sau khi tôi nhận được một số câu trả lời tốt, vì vậy ông ít nhất học một cái gì đó.

+0

số lượng trong nhóm của bạn? bạn đã bao giờ thèm khát về "xem xét mã" như là một cơ sở cho "cải tiến" (tiền, vị trí, uy tín) Codeview không phải là "kiểm soát quá" mà là một cách chất lượng phần mềm. –

+0

Chúng tôi không ở trong một đội, anh ấy chỉ là bạn của tôi, người cũng viết C và C++. Tôi thực sự là một sinh viên. – bodacydo

Trả lời

9

bạn sẽ nhận được một vấn đề, khi bạn gọi một chức năng giữa name() và printf(), mà chính nó sử dụng ngăn xếp

char *fun(char *what) { 
    char res[10]; 
    strncpy(res, what, 9); 
    return res; 
} 

main() { 
    char *r1 = fun("bla"); 
    char *r2 = fun("blubber"); 
    printf("'%s' is bla and '%s' is blubber", r1, r2); 
} 
+0

tôi không có trình biên dịch trong tay, vì vậy tôi không biết điều này sẽ cho kết quả, nhưng tôi hy vọng nó là 'blubber' là bla và 'blubber' là blubber. –

+0

Xác nhận theo gcc – cobbal

+0

cũng thú vị để lưu ý: trên một máy tôi nhận được '' blubber 'là bla và' blubber 'là blubber' trên mac của tôi tôi nhận được '' 'là bla và' 'là blubber' – cobbal

4

Ngay khi phạm vi của hàm kết thúc, tức là sau khi đóng ngoặc nhọn} của hàm, bộ nhớ được cấp phát (trên ngăn xếp) cho tất cả biến cục bộ sẽ bị bỏ lại. Vì vậy, trở về con trỏ đến một số bộ nhớ mà không còn hợp lệ gọi hành vi không xác định. Ngoài ra, bạn có thể nói rằng tuổi thọ biến cục bộ được kết thúc khi hàm kết thúc thực hiện.

Bạn cũng có thể đọc thêm chi tiết HERE.

+2

Điều này không hoàn toàn đúng: bộ nhớ được đề cập nằm trên ngăn xếp, vì vậy bộ nhớ không được phát hành, nó được để nguyên như trước, nhưng nó sẽ bị ghi đè trong các cuộc gọi hàm tiếp theo. –

+0

Tôi nói nó được phát hành bởi trình biên dịch, có nghĩa là trình biên dịch sẽ không giữ nó cho mục đích. Dù sao thì hãy để tôi cập nhật nó. – Incognito

12

Bạn của bạn sai.

name sẽ trả lại con trỏ cho ngăn xếp cuộc gọi. Khi bạn gọi printf, không có cách nào ngăn cách ngăn xếp đó sẽ bị ghi đè trước khi dữ liệu tại con trỏ được truy cập. Nó có thể hoạt động trên trình biên dịch và máy của anh ta, nhưng nó sẽ không hoạt động trên tất cả chúng.

Bạn của bạn tuyên bố rằng sau khi name trả về, "không có gì xảy ra trừ khi in". printf chính nó là một cuộc gọi hàm khác, với ai biết có bao nhiêu sự phức tạp bên trong nó. Một thỏa thuận lớn đang xảy ra trước khi dữ liệu được in.

Ngoài ra, mã chưa bao giờ kết thúc, mã sẽ được sửa đổi và thêm vào. Mã "không làm gì" bây giờ sẽ làm điều gì đó một khi nó đã thay đổi, và thủ thuật chặt chẽ của bạn sẽ sụp đổ.

Trả về một con trỏ đến dữ liệu cục bộ là một công thức cho thảm họa.

+1

+1 cho "công thức cho thảm họa" –

2

tôi phản đối số sẽ là:

  • không bao giờ là OK để viết mã với hành vi undefined,
  • bao lâu trước khi người khác sử dụng chức năng rằng trong bối cảnh khác nhau,
  • ngôn ngữ cung cấp cơ sở để làm điều tương tự một cách hợp pháp (và có thể hiệu quả hơn)
+1

các đối số này không hoạt động cho một người bướng bỉnh !!!!!! anh ấy sẽ chỉ nói "nó hoạt động trên trình biên dịch của tôi, tôi không quan tâm (hoặc biết) ra ub" và tiếp tục viết mã của mình. miễn là mã hoạt động, anh ấy chỉ viết theo cách của mình. :( nếu tôi có thể hiển thị một ví dụ bị lỗi, có thể sẽ dạy cho anh ta – bodacydo

+1

Đây là một ví dụ tốt mà tôi muốn xây dựng. Sắp xếp cho ngăn xếp có con trỏ đến hàm 'puts()' và định dạng chuỗi/yc: ", sau đó gọi hàm sử dụng con trỏ hàm với con trỏ char làm đối số của nó. Bây giờ, tìm cách 'hỏng' (tôi sử dụng từ đó một cách lỏng lẻo vì nội dung chưa được xác định) ngăn xếp ở giữa con trỏ tới 'puts()' được thay thế bởi địa chỉ của 'system()', và đưa nó cho bạn của bạn. :-) –

+0

R .. - đó có lẽ là ví dụ tốt nhất. Tôi sẽ làm xáo trộn nó một chút và đưa nó cho anh ta. Điều đó sẽ dạy cho anh ta bài học tốt. – bodacydo

1

Bạn đúng - n cuộc sống trên ngăn xếp và vì vậy có thể biến mất ngay sau khi hàm trả về.

Mã của bạn bè của bạn có thể chỉ hoạt động vì vị trí bộ nhớ mà n trỏ đến chưa bị hỏng (chưa!).

+0

Anh ấy phải may mắn. tôi gửi cho anh ta toàn bộ chủ đề này. Ví dụ của Peter hoàn hảo cho thấy nó không hoạt động. – bodacydo

2

Nó xác định hành vi và giá trị có thể dễ dàng bị phá hủy trước khi nó thực sự là in. printf(), mà chỉ là một chức năng bình thường, có thể sử dụng một số biến địa phương hoặc gọi các chức năng khác trước khi chuỗi thực sự được in. Vì những hành động này sử dụng ngăn xếp nên chúng có thể dễ dàng làm hỏng giá trị.

Nếu mã xảy ra để in giá trị chính xác tùy thuộc vào việc thực hiện printf() và cách gọi hàm hoạt động trên trình biên dịch/nền tảng bạn đang sử dụng (tham số/địa chỉ/biến nào được đặt trên ngăn xếp, ...) . Ngay cả khi mã xảy ra với "công việc" trên máy tính của bạn với một số cài đặt trình biên dịch, nó không còn chắc chắn rằng nó sẽ hoạt động ở bất kỳ nơi nào khác hoặc trong các điều kiện biên giới hơi khác nhau.

1

Như những người khác đã chỉ ra không phải là bất hợp pháp để làm điều này, nhưng một ý tưởng tồi vì dữ liệu trả về nằm trên phần không được sử dụng của ngăn xếp và có thể bị ghi đè bất cứ lúc nào bằng các cuộc gọi chức năng khác.

Đây là một phản ví dụ mà bị treo trên hệ thống của tôi nếu biên dịch với tối ưu hóa bật:

char * name() 
{ 
    char n[] = "Hello World"; 
    return n; 
} 

void test (char * arg) 
{ 
    // msg and arg will reside roughly at the same memory location. 
    // so changing msg will change arg as well: 
    char msg[100]; 

    // this will override whatever arg points to. 
    strcpy (msg, "Logging: "); 

    // here we access the overridden data. A bad idea! 
    strcat (msg, arg); 

    strcat (msg, "\n"); 
    printf (msg); 
} 

int main() 
{ 
    char * n = name(); 
    test (n); 
    return 0; 
} 
0

gcc: main.c: Trong chức năng 'name': main.c: 4: cảnh báo: hàm trả về địa chỉ của biến cục bộ

Bất cứ nơi nào nó có thể được thực hiện như thế (nhưng nó không phải là mã sexy: p):

char *name() 
{ 
    static char n[10] = "bodacydo!"; 
    return n; 
} 

int main() 
{ 
    char *n = name(); 

    printf("%s\n", n); 
} 

Warning nó không đề an toàn.

0

Trả về con trỏ tới biến cục bộ là sai, ngay cả khi nó dường như hoạt động trong một số trường hợp hiếm hoi.

Biến cục bộ (tự động) có thể được cấp phát từ ngăn xếp hoặc từ sổ đăng ký.

  • Nếu nó được cấp phát từ ngăn xếp, nó sẽ bị ghi đè ngay khi cuộc gọi hàm tiếp theo (như printf) được thực hiện hoặc nếu xảy ra gián đoạn.
  • Nếu biến được cấp từ sổ đăng ký, thậm chí không thể trỏ con trỏ đến biến đó.

Ngay cả khi ứng dụng là "chỉ một luồng", thì các ngắt có thể sử dụng ngăn xếp. Để tương đối an toàn, bạn nên tắt các ngắt. Nhưng nó không thể vô hiệu hóa NMI (Non Maskable Interrupt), vì vậy bạn không bao giờ có thể an toàn.

0

Mặc dù bạn không thể trả về con trỏ cho biến ngăn xếp cục bộ được khai báo bên trong một hàm, tuy nhiên bạn có thể cấp phát bộ nhớ bên trong hàm bằng cách sử dụng malloc và sau đó trả về con trỏ tới khối đó. Có lẽ đây là ý bạn của bạn?

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

char* getstr(){ 
    char* ret=malloc(sizeof(char)*15); 
    strcpy(ret,"Hello World"); 
    return ret; 
} 
int main(){ 
    char* answer=getstr(); 
    printf("%s\n", answer); 
    free(answer); 
    return 0; 
} 
+0

Không, anh ấy nói chính xác những gì tôi đã nói. Những gì bạn dán là những gì mọi lập trình viên nên làm. :) Anh ấy là một thằng ngốc và không biết điều đó. – bodacydo

1

Bạn nói đúng, bạn của bạn đã sai. Dưới đây là một phản ví dụ đơn giản:

char *n = name(); 
printf("(%d): %s\n", 1, n); 
-2

Nếu chúng ta lấy đoạn mã u đã ....

char *name() { 
    char n[10] = "bodacydo!"; 
    return n; 
} 
int main() { 
    char *n = name(); 
    printf("%s\n", n); 
} 

nó okay để sử dụng var địa phương trong printf() trong chính 'coz ở đây chúng tôi đang sử dụng một chuỗi chữ mà một lần nữa không phải là một cái gì đó cục bộ để đặt tên().

Nhưng bây giờ cho phép nhìn vào một mã hơi khác nhau

class SomeClass { 
    int *i; 
public: 
    SomeClass() { 
     i = new int(); 
     *i = 23; 
    } 
    ~SomeClass() { 
     delete i; 
     i = NULL; 
    } 
    void print() { 
     printf("%d", *i); 
    } 
}; 
SomeClass *name() { 
    SomeClass s; 
    return &s; 
} 
int main() { 
    SomeClass *n = name(); 
    n->print(); 
} 

Trong trường hợp này khi chức năng tên() trả về SomeClass destructor sẽ được gọi và var viên tôi đã có thể được deallocated và thiết lập để NULL. Vì vậy, khi chúng ta gọi print() trong chính mặc dù kể từ khi mem chỉ bởi n không được ghi đè (tôi giả định rằng) các cuộc gọi in sẽ sụp đổ khi nó cố gắng để de-tham chiếu một con trỏ NULL.

Vì vậy, theo cách mà đoạn mã ur rất có thể sẽ không bị lỗi nhưng rất có thể sẽ thất bại nếu các đối tượng deconstructor đang thực hiện một số tài nguyên deinitialization và chúng tôi đang sử dụng nó sau đó.

Hy vọng nó giúp

+0

Không, ví dụ đầu tiên hoàn toàn không ổn. Chuỗi ký tự được sử dụng để khởi tạo một mảng, khác biệt với 'char * n =" bodacydo! ";'. –

0

Con đường tôi nhìn thấy nó, bạn có ba tùy chọn chính vì cái này là nguy hiểm và sử dụng hành vi undefined:

thay thế: char n[10] = "bodacydo!"

với: static char n[10] = "bodacydo!"

này sẽ cho kết quả không mong muốn nếu bạn sử dụng cùng chức năng nhiều lần trong hàng trong khi cố gắng duy trì các giá trị chứa trong đó.

thay thế:
char n[10] = "bodacydo!"

với:
char *n = new char[10]; *n = "bodacydo!"

Với sẽ khắc phục vấn đề nói trên, nhưng sau đó bạn sẽ cần phải xoá bộ nhớ heap hoặc bắt đầu phát sinh rò rỉ bộ nhớ.

Hoặc cuối cùng:

thay thế: char n[10] = "bodacydo!";

với: shared_ptr&lt;char&gt; n(new char[10]) = "bodacydo!";

nào giúp bạn thoát khỏi việc phải xóa bộ nhớ heap, nhưng sau đó bạn sẽ phải thay đổi kiểu trả về và char * n trong chính một shared_prt là tốt để bàn giao quản lý của con trỏ. Nếu bạn không tắt nó, phạm vi của shared_ptr sẽ kết thúc và giá trị được lưu trữ trong con trỏ được đặt thành NULL.

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