Quản lý bộ nhớ C


90

Tôi luôn nghe nói rằng trong C, bạn phải thực sự xem cách bạn quản lý bộ nhớ. Và tôi vẫn đang bắt đầu học C, nhưng cho đến nay, tôi chưa phải làm bất kỳ bộ nhớ nào để quản lý các hoạt động liên quan cả .. Tôi luôn tưởng tượng phải giải phóng các biến và làm đủ thứ xấu xí. Nhưng điều này dường như không phải là trường hợp.

Ai đó có thể chỉ cho tôi (với các ví dụ về mã) một ví dụ về thời điểm bạn sẽ phải thực hiện một số "quản lý bộ nhớ" không?


Nơi tốt để tìm hiểu G4G
EsmaeelE

Câu trả lời:


230

Có hai nơi mà các biến có thể được đưa vào bộ nhớ. Khi bạn tạo một biến như thế này:

int  a;
char c;
char d[16];

Các biến được tạo trong " ngăn xếp ". Các biến ngăn xếp tự động được giải phóng khi chúng vượt ra khỏi phạm vi (nghĩa là khi mã không thể tiếp cận chúng nữa). Bạn có thể nghe thấy chúng được gọi là biến "tự động", nhưng điều đó đã không còn hợp thời.

Nhiều ví dụ mới bắt đầu sẽ chỉ sử dụng các biến ngăn xếp.

Ngăn xếp rất hay vì nó tự động, nhưng nó cũng có hai nhược điểm: (1) Trình biên dịch cần biết trước độ lớn của các biến và (b) không gian ngăn xếp có phần hạn chế. Ví dụ: trong Windows, theo cài đặt mặc định cho trình liên kết Microsoft, ngăn xếp được đặt thành 1 MB và không phải tất cả đều có sẵn cho các biến của bạn.

Nếu bạn không biết mảng của mình lớn như thế nào tại thời điểm biên dịch, hoặc nếu bạn cần một mảng hoặc cấu trúc lớn, bạn cần "kế hoạch B".

Kế hoạch B được gọi là " đống ". Bạn thường có thể tạo các biến lớn như Hệ điều hành sẽ cho phép bạn, nhưng bạn phải tự làm. Các bài đăng trước đó đã chỉ cho bạn một cách bạn có thể làm, mặc dù có những cách khác:

int size;
// ...
// Set size to some value, based on information available at run-time. Then:
// ...
char *p = (char *)malloc(size);

(Lưu ý rằng các biến trong heap không được thao tác trực tiếp mà thông qua con trỏ)

Khi bạn tạo một biến heap, vấn đề là trình biên dịch không thể biết khi nào bạn hoàn thành với nó, vì vậy bạn mất tính năng tự động phát hành. Đó là nơi "giải phóng thủ công" mà bạn đang đề cập đến. Mã của bạn bây giờ chịu trách nhiệm quyết định khi nào biến không cần thiết nữa và giải phóng nó để bộ nhớ có thể được sử dụng cho các mục đích khác. Đối với trường hợp trên, với:

free(p);

Điều làm cho lựa chọn thứ hai này trở thành "công việc kinh doanh khó chịu" là không phải lúc nào bạn cũng dễ dàng biết được khi nào biến số không cần thiết nữa. Quên phát hành một biến khi bạn không cần đến nó sẽ khiến chương trình của bạn sử dụng nhiều bộ nhớ hơn mà nó cần. Tình trạng này được gọi là "rò rỉ". Bộ nhớ "bị rò rỉ" không thể được sử dụng cho bất cứ điều gì cho đến khi chương trình của bạn kết thúc và hệ điều hành khôi phục tất cả tài nguyên của nó. Thậm chí có thể xảy ra các vấn đề tồi tệ hơn nếu bạn phát hành một biến đống do nhầm lẫn trước khi bạn thực sự xử lý xong.

Trong C và C ++, bạn có trách nhiệm dọn dẹp các biến đống của mình như được hiển thị ở trên. Tuy nhiên, có những ngôn ngữ và môi trường như ngôn ngữ Java và .NET như C # sử dụng một cách tiếp cận khác, trong đó heap được tự dọn dẹp. Phương pháp thứ hai này, được gọi là "thu gom rác", dễ dàng hơn nhiều đối với nhà phát triển nhưng bạn phải trả một khoản phạt về chi phí và hiệu suất. Đó là một sự cân bằng.

(Tôi đã lược qua nhiều chi tiết để đưa ra một câu trả lời đơn giản hơn, nhưng hy vọng rằng câu trả lời ở cấp độ cao hơn)


3
Nếu bạn muốn đặt thứ gì đó vào ngăn xếp nhưng không biết kích thước của nó tại thời điểm biên dịch, alloca () có thể phóng to khung ngăn xếp để có chỗ. Không có freea (), toàn bộ khung ngăn xếp được bật lên khi hàm trả về. Sử dụng alloca () cho các phân bổ lớn sẽ gặp nhiều nguy hiểm.
DGentry

1
Có lẽ bạn có thể thêm một hoặc hai câu về vị trí bộ nhớ của các biến toàn cục
Michael Käfer

Trong C không bao giờ trả lại malloc(), nguyên nhân của nó UB, (char *)malloc(size);hãy xem stackoverflow.com/questions/605845/…
EsmaeelE

17

Đây là một ví dụ. Giả sử bạn có một hàm strdup () sao chép một chuỗi:

char *strdup(char *src)
{
    char * dest;
    dest = malloc(strlen(src) + 1);
    if (dest == NULL)
        abort();
    strcpy(dest, src);
    return dest;
}

Và bạn gọi nó như thế này:

main()
{
    char *s;
    s = strdup("hello");
    printf("%s\n", s);
    s = strdup("world");
    printf("%s\n", s);
}

Bạn có thể thấy rằng chương trình hoạt động, nhưng bạn đã cấp phát bộ nhớ (thông qua malloc) mà không giải phóng nó. Bạn đã mất con trỏ tới khối bộ nhớ đầu tiên khi bạn gọi strdup lần thứ hai.

Đây không phải là vấn đề lớn đối với lượng bộ nhớ nhỏ này, nhưng hãy xem xét trường hợp:

for (i = 0; i < 1000000000; ++i)  /* billion times */
    s = strdup("hello world");    /* 11 bytes */

Bây giờ bạn đã sử dụng hết 11 GB bộ nhớ (có thể nhiều hơn, tùy thuộc vào trình quản lý bộ nhớ của bạn) và nếu bạn không gặp sự cố thì quá trình của bạn có thể đang chạy khá chậm.

Để khắc phục, bạn cần gọi free () cho mọi thứ nhận được bằng malloc () sau khi bạn sử dụng xong:

s = strdup("hello");
free(s);  /* now not leaking memory! */
s = strdup("world");
...

Hy vọng ví dụ này sẽ giúp!


Tôi thích câu trả lời này hơn. Nhưng tôi có một câu hỏi nhỏ. Tôi mong đợi điều gì đó như thế này sẽ được giải quyết bằng các thư viện, không có thư viện nào bắt chước chặt chẽ các kiểu dữ liệu cơ bản và thêm các tính năng giải phóng bộ nhớ cho chúng để khi các biến được sử dụng, chúng cũng được giải phóng tự động?
Lorenzo

Không có gì là một phần của tiêu chuẩn. Nếu bạn đi vào C ++, bạn sẽ nhận được các chuỗi và vùng chứa thực hiện quản lý bộ nhớ tự động.
Mark Harrison

Tôi hiểu rồi, vậy có một số lib của bên thứ 3? Bạn có thể vui lòng đặt tên cho họ?
Lorenzo

9

Bạn phải thực hiện "quản lý bộ nhớ" khi bạn muốn sử dụng bộ nhớ trên heap hơn là ngăn xếp. Nếu bạn không biết độ lớn để tạo một mảng cho đến thời gian chạy, thì bạn phải sử dụng heap. Ví dụ: bạn có thể muốn lưu trữ một cái gì đó trong một chuỗi, nhưng không biết nội dung của nó sẽ lớn như thế nào cho đến khi chương trình được chạy. Trong trường hợp đó, bạn sẽ viết một cái gì đó như thế này:

 char *string = malloc(stringlength); // stringlength is the number of bytes to allocate

 // Do something with the string...

 free(string); // Free the allocated memory

5

Tôi nghĩ cách ngắn gọn nhất để trả lời câu hỏi là xem xét vai trò của con trỏ trong C. Con trỏ là một cơ chế nhẹ nhưng mạnh mẽ, mang lại cho bạn sự tự do to lớn với cái giá phải trả là khả năng tự bắn vào chân mình.

Trong C, trách nhiệm đảm bảo các con trỏ trỏ tới bộ nhớ mà bạn sở hữu là của bạn và của riêng bạn. Điều này đòi hỏi một cách tiếp cận có tổ chức và kỷ luật, trừ khi bạn từ bỏ con trỏ, điều này khiến bạn khó viết C hiệu quả.

Các câu trả lời đã đăng cho đến nay tập trung vào phân bổ biến tự động (ngăn xếp) và đống. Sử dụng cấp phát ngăn xếp tạo ra bộ nhớ được quản lý tự động và thuận tiện, nhưng trong một số trường hợp (bộ đệm lớn, thuật toán đệ quy) nó có thể dẫn đến vấn đề khủng khiếp là tràn ngăn xếp. Việc biết chính xác lượng bộ nhớ bạn có thể phân bổ trên ngăn xếp phụ thuộc rất nhiều vào hệ thống. Trong một số trường hợp nhúng, vài chục byte có thể là giới hạn của bạn, trong một số trường hợp máy tính để bàn, bạn có thể sử dụng megabyte một cách an toàn.

Phân bổ đống ít vốn có đối với ngôn ngữ. Về cơ bản, nó là một tập hợp các lệnh gọi thư viện cấp cho bạn quyền sở hữu một khối bộ nhớ có kích thước nhất định cho đến khi bạn sẵn sàng trả lại ('miễn phí') nó. Nghe có vẻ đơn giản, nhưng lại gắn liền với những nỗi đau khôn nguôi của các lập trình viên. Các vấn đề rất đơn giản (giải phóng cùng một bộ nhớ hai lần, hoặc hoàn toàn không [rò rỉ bộ nhớ], không cấp phát đủ bộ nhớ [tràn bộ đệm], v.v.) nhưng khó tránh và gỡ lỗi. Một cách tiếp cận kỷ luật cao là hoàn toàn bắt buộc trong thực hành nhưng tất nhiên ngôn ngữ không thực sự bắt buộc nó.

Tôi muốn đề cập đến một kiểu cấp phát bộ nhớ khác đã bị các bài viết khác bỏ qua. Có thể cấp phát tĩnh các biến bằng cách khai báo chúng bên ngoài bất kỳ hàm nào. Tôi nghĩ rằng kiểu phân bổ này nói chung là tệ vì nó được sử dụng bởi các biến toàn cục. Tuy nhiên, không có gì nói rằng cách duy nhất để sử dụng bộ nhớ được cấp phát theo cách này là như một biến toàn cục vô kỷ luật trong một mớ mã spaghetti. Phương pháp phân bổ tĩnh có thể được sử dụng đơn giản để tránh một số cạm bẫy của phương pháp phân bổ đống và tự động. Một số lập trình viên C ngạc nhiên khi biết rằng các chương trình trò chơi và nhúng C lớn và phức tạp đã được xây dựng mà hoàn toàn không sử dụng phân bổ heap.


4

Có một số câu trả lời tuyệt vời ở đây về cách phân bổ và giải phóng bộ nhớ, và theo tôi khía cạnh thách thức hơn khi sử dụng C là đảm bảo rằng bộ nhớ duy nhất bạn sử dụng là bộ nhớ bạn đã cấp phát - nếu điều này không được thực hiện đúng những gì bạn kết thúc liên quan đến người anh em họ của trang web này - một sự cố tràn bộ đệm - và bạn có thể đang ghi đè bộ nhớ đang được sử dụng bởi một ứng dụng khác, với kết quả rất khó lường.

Một ví dụ:

int main() {
    char* myString = (char*)malloc(5*sizeof(char));
    myString = "abcd";
}

Tại thời điểm này, bạn đã phân bổ 5 byte cho chuỗi myString và điền nó bằng "abcd \ 0" (chuỗi kết thúc bằng null - \ 0). Nếu phân bổ chuỗi của bạn là

myString = "abcde";

Bạn sẽ gán "abcde" trong 5 byte mà bạn đã phân bổ cho chương trình của mình và ký tự null ở cuối sẽ được đặt ở cuối phần này - một phần bộ nhớ chưa được cấp phát cho bạn sử dụng và có thể miễn phí, nhưng cũng có thể được sử dụng bởi một ứng dụng khác - Đây là phần quan trọng của quản lý bộ nhớ, nơi một sai lầm sẽ gây ra hậu quả không thể đoán trước (và đôi khi không thể lặp lại).


Ở đây bạn phân bổ 5 byte. Làm mất nó bằng cách gán một con trỏ. Bất kỳ nỗ lực nào để giải phóng con trỏ này đều dẫn đến hành vi không xác định. Lưu ý C-Strings không quá tải toán tử = không có bản sao.
Martin York

Tuy nhiên, nó thực sự phụ thuộc vào malloc bạn đang sử dụng. Nhiều toán tử malloc sắp xếp thành 8 byte. Vì vậy, nếu malloc này đang sử dụng hệ thống đầu trang / chân trang, thì malloc sẽ dành 5 + 4 * 2 (4 byte cho cả đầu trang và chân trang). Đó sẽ là 13 byte và malloc sẽ chỉ cung cấp cho bạn thêm 3 byte để căn chỉnh. Tôi không nói rằng nên sử dụng cái này, bởi vì nó sẽ chỉ những hệ thống có malloc hoạt động như thế này, nhưng ít nhất điều quan trọng là phải biết tại sao làm sai điều gì đó có thể hoạt động.
kodai

Loki: Tôi đã chỉnh sửa câu trả lời để sử dụng strcpy()thay vì =; Tôi cho rằng đó là ý định của Chris BC.
echristopherson

Tôi tin rằng trong các nền tảng hiện đại, bảo vệ bộ nhớ phần cứng ngăn không cho các quy trình không gian người dùng ghi đè không gian địa chỉ của các quy trình khác; thay vào đó bạn sẽ gặp lỗi phân đoạn. Nhưng đó không phải là một phần của C per se.
echristopherson

4

Một điều cần nhớ là luôn khởi tạo con trỏ của bạn thành NULL, vì con trỏ chưa được khởi tạo có thể chứa địa chỉ bộ nhớ hợp lệ giả ngẫu nhiên có thể khiến lỗi con trỏ tiếp tục âm thầm. Bằng cách thực thi một con trỏ được khởi tạo bằng NULL, bạn luôn có thể bắt được nếu bạn đang sử dụng con trỏ này mà không khởi tạo nó. Nguyên nhân là do hệ điều hành "chuyển" địa chỉ ảo 0x00000000 thành các ngoại lệ bảo vệ chung để bẫy việc sử dụng con trỏ rỗng.


2

Ngoài ra, bạn có thể muốn sử dụng cấp phát bộ nhớ động khi bạn cần xác định một mảng lớn, chẳng hạn như int [10000]. Bạn không thể chỉ đặt nó trong ngăn xếp vì sau đó, hm ... bạn sẽ nhận được tràn ngăn xếp.

Một ví dụ điển hình khác là việc triển khai cấu trúc dữ liệu, chẳng hạn như danh sách liên kết hoặc cây nhị phân. Mình không có mã mẫu để dán ở đây nhưng bạn có thể google dễ dàng.


2

(Tôi đang viết vì tôi cảm thấy các câu trả lời cho đến nay vẫn chưa được hoàn thiện.)

Lý do bạn phải quản lý bộ nhớ đáng nói là khi bạn có một vấn đề / giải pháp yêu cầu bạn tạo ra các cấu trúc phức tạp. (Nếu chương trình của bạn gặp sự cố nếu bạn phân bổ nhiều không gian trên ngăn xếp cùng một lúc, đó là một lỗi.) Thông thường, cấu trúc dữ liệu đầu tiên bạn cần tìm hiểu là một số loại danh sách . Đây là một liên kết duy nhất, ngoài đầu tôi:

typedef struct listelem { struct listelem *next; void *data;} listelem;

listelem * create(void * data)
{
   listelem *p = calloc(1, sizeof(listelem));
   if(p) p->data = data;
   return p;
}

listelem * delete(listelem * p)
{
   listelem next = p->next;
   free(p);
   return next;
}

void deleteall(listelem * p)
{
  while(p) p = delete(p);
}

void foreach(listelem * p, void (*fun)(void *data) )
{
  for( ; p != NULL; p = p->next) fun(p->data);
}

listelem * merge(listelem *p, listelem *q)
{
  while(p != NULL && p->next != NULL) p = p->next;
  if(p) {
    p->next = q;
    return p;
  } else
    return q;
}

Đương nhiên, bạn muốn một vài chức năng khác, nhưng về cơ bản, đây là những gì bạn cần quản lý bộ nhớ. Tôi nên chỉ ra rằng có một số thủ thuật có thể thực hiện được với quản lý bộ nhớ "thủ công", ví dụ:

  • Sử dụng thực tế rằng malloc được đảm bảo (theo tiêu chuẩn ngôn ngữ) để trả về một con trỏ chia hết cho 4,
  • phân bổ thêm không gian cho một số mục đích độc ác của riêng bạn,
  • tạo nhóm bộ nhớ s ..

Nhận một trình gỡ lỗi tốt ... Chúc may mắn!


Học cấu trúc dữ liệu là bước quan trọng tiếp theo để hiểu về quản lý bộ nhớ. Học các thuật toán để chạy các cấu trúc này một cách thích hợp sẽ chỉ cho bạn các phương pháp thích hợp để vượt qua những tổn thương này. Đây là lý do tại sao bạn sẽ tìm thấy Cấu trúc dữ liệu và Thuật toán được dạy trong các khóa học giống nhau.
aj.toulan

0

@ Euro Micelli

Một điều phủ định cần thêm là các con trỏ đến ngăn xếp không còn hợp lệ khi hàm trả về, vì vậy bạn không thể trả về một con trỏ đến một biến ngăn xếp từ một hàm. Đây là một lỗi phổ biến và là lý do chính khiến bạn không thể vượt qua chỉ với các biến ngăn xếp. Nếu hàm của bạn cần trả về một con trỏ, thì bạn phải xử lý và xử lý việc quản lý bộ nhớ.


0

@ Ted Percival :
... bạn không cần ép giá trị trả về của malloc ().

Bạn đúng, tất nhiên. Tôi tin rằng điều đó luôn đúng, mặc dù tôi không có bản sao K&R để kiểm tra.

Tôi không thích nhiều chuyển đổi ngầm trong C, vì vậy tôi có xu hướng sử dụng phép thuật để làm cho "ma thuật" hiển thị rõ ràng hơn. Đôi khi nó giúp dễ đọc, đôi khi không, và đôi khi nó gây ra lỗi im lặng được trình biên dịch bắt. Tuy nhiên, tôi không có quan điểm mạnh mẽ về điều này, cách này hay cách khác.

Điều này đặc biệt có thể xảy ra nếu trình biên dịch của bạn hiểu các bình luận kiểu C ++.

Yeah ... bạn đã bắt gặp tôi ở đó. Tôi dành nhiều thời gian hơn trong C ++ so với C. Cảm ơn bạn đã chú ý đến điều đó.


@echristopherson, cảm ơn. Bạn đúng - nhưng xin lưu ý rằng Q / A này có từ tháng 8 năm 2008, trước khi Stack Overflow thậm chí còn ở bản Beta công khai. Hồi đó, chúng tôi vẫn đang tìm cách hoạt động của trang web. Định dạng của Câu hỏi / Câu trả lời này không nhất thiết phải được xem như một mô hình cho cách sử dụng SO. Cảm ơn!
Euro Micelli,

À, cảm ơn vì đã chỉ ra điều đó - lúc đó tôi không nhận ra rằng khía cạnh của trang web vẫn còn ổn định.
echristopherson

0

Trong C, bạn thực sự có hai sự lựa chọn khác nhau. Một, bạn có thể để hệ thống quản lý bộ nhớ cho bạn. Ngoài ra, bạn có thể làm điều đó một mình. Nói chung, bạn sẽ muốn gắn bó với cái trước càng lâu càng tốt. Tuy nhiên, bộ nhớ được quản lý tự động trong C rất hạn chế và bạn sẽ cần phải quản lý bộ nhớ theo cách thủ công trong nhiều trường hợp, chẳng hạn như:

a. Bạn muốn biến tồn tại lâu hơn các hàm và bạn không muốn có biến toàn cục. Ví dụ:

cặp cấu trúc {
   int val;
   cặp struct * tiếp theo;
}

cặp struct * new_pair (int val) {
   cặp struct * np = malloc (sizeof (cặp struct));
   np-> val = val;
   np-> next = NULL;
   trả về np;
}

b. bạn muốn có bộ nhớ được cấp phát động. Ví dụ phổ biến nhất là mảng không có độ dài cố định:

int * my_special_array;
my_special_array = malloc (sizeof (int) * number_of_element);
cho (i = 0; i

c. Bạn muốn làm điều gì đó THỰC SỰ dơ bẩn. Ví dụ: tôi muốn có một cấu trúc đại diện cho nhiều loại dữ liệu và tôi không thích liên minh (union trông quá lộn xộn):

cấu trúc dữ liệu { int data_type; data_in_mem dài; } struct động vật {/ * cái gì đó * /}; struct person {/ * some other thing * /}; struct động vật * read_animal (); struct người * read_ person (); / * Trong chính * / mẫu dữ liệu cấu trúc; sampe.data_type = input_type; switch (input_type) { trường hợp DATA_PERSON: sample.data_in_mem = read_ person (); phá vỡ; trường hợp DATA_ANIMAL: sample.data_in_mem = read_animal (); mặc định: printf ("Oh hoh! Tôi cảnh báo bạn, điều đó một lần nữa và tôi sẽ làm lỗi hệ điều hành của bạn"); }

Hãy xem, một giá trị dài đủ để chứa BẤT CỨ ĐIỀU GÌ. Chỉ cần nhớ để giải phóng nó, hoặc bạn sẽ hối tiếc. Đây là một trong những thủ thuật yêu thích của tôi để giải trí trong C: D.

Tuy nhiên, nói chung, bạn sẽ muốn tránh xa các thủ thuật yêu thích của mình (T___T). Bạn SẼ phá vỡ hệ điều hành của mình, sớm hay muộn, nếu bạn sử dụng chúng quá thường xuyên. Miễn là bạn không sử dụng * phân bổ và miễn phí, có thể an toàn để nói rằng bạn vẫn còn trinh và mã trông vẫn đẹp.


"Thấy chưa, giá trị dài đủ để chứa BẤT CỨ ĐIỀU GÌ" -: / bạn đang nói gì vậy, trên hầu hết các hệ thống, giá trị dài là 4 byte, giống hệt như một số nguyên. Lý do duy nhất mà nó phù hợp với các con trỏ ở đây là vì kích thước của long sẽ giống với kích thước của con trỏ. Tuy nhiên, bạn thực sự nên sử dụng void *.
Score_Under

-2

Chắc chắn rồi. Nếu bạn tạo một đối tượng tồn tại bên ngoài phạm vi mà bạn sử dụng nó. Đây là một ví dụ có sẵn (hãy nhớ rằng cú pháp của tôi sẽ bị tắt; C của tôi đã cũ, nhưng ví dụ này vẫn sẽ minh họa khái niệm):

class MyClass
{
   SomeOtherClass *myObject;

   public MyClass()
   {
      //The object is created when the class is constructed
      myObject = (SomeOtherClass*)malloc(sizeof(myObject));
   }

   public ~MyClass()
   {
      //The class is destructed
      //If you don't free the object here, you leak memory
      free(myObject);
   }

   public void SomeMemberFunction()
   {
      //Some use of the object
      myObject->SomeOperation();
   }


};

Trong ví dụ này, tôi đang sử dụng một đối tượng kiểu SomeOtherClass trong suốt thời gian tồn tại của MyClass. Đối tượng SomeOtherClass được sử dụng trong một số chức năng, vì vậy tôi đã cấp phát động bộ nhớ: đối tượng SomeOtherClass được tạo khi MyClass được tạo, được sử dụng nhiều lần trong vòng đời của đối tượng và sau đó được giải phóng sau khi MyClass được giải phóng.

Rõ ràng nếu đây là mã thực, sẽ không có lý do gì (ngoài việc có thể tiêu thụ bộ nhớ ngăn xếp) để tạo myObject theo cách này, nhưng kiểu tạo / hủy đối tượng này trở nên hữu ích khi bạn có nhiều đối tượng và muốn kiểm soát tốt khi chúng được tạo và bị phá hủy (ví dụ như để ứng dụng của bạn không hút hết 1GB RAM trong suốt thời gian tồn tại của nó) và trong môi trường Windowed, điều này là khá bắt buộc, như các đối tượng mà bạn tạo (các nút, chẳng hạn) , cần phải tồn tại tốt bên ngoài phạm vi của bất kỳ hàm cụ thể nào (hoặc thậm chí cả lớp ').


1
Heh, yeah, đó là C ++ phải không? Thật ngạc nhiên là phải mất năm tháng mới có người gọi cho tôi.
TheSmurf
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.