2009-10-16 37 views
10

Ví dụ đầu tiên không hoạt động khi bạn đi để xóa con trỏ. Chương trình này hoặc treo khi tôi thêm terminator null hoặc không có nó tôi nhận được:Tại sao phải trỏ đến một mảng char cần strcpy để gán các ký tự cho mảng của nó và việc gán ngoặc kép sẽ không hoạt động?

Debug Assertion Failed Expression: _BLOCK_TYPE_IS_VALID(pHead->nBlockUse) từ Visual Studio 2008

//Won't work when deleting pointer: 
    char *at = new char [3]; 
    at = "tw"; // <-- not sure what's going on here that strcpy does differently 
    at[2] = '\0'; // <-- causes program to hang 
    delete at; 

//Works fine when deleting pointer: 
    char *at = new char [3]; 
    strcpy(at,"t"); 
    at[1] = 'w'; 
    at[2] = '\0'; 
    delete at; 

Vì vậy, những gì đang xảy ra khi tôi sử dụng dấu ngoặc kép thay vì strcpy? Cả hai người trong số họ sẽ cout chuỗi hoàn hảo và trình gỡ rối không hiển thị bất cứ điều gì khác nhau.

+6

Tôi giả sử bạn có nghĩa là 'xóa []' – MSalters

+0

Bạn đã trả lời câu hỏi của riêng mình. strcpy() gán các ký tự cho mảng. = gán một mảng mới. Trình gỡ lỗi thực sự cho thấy một cái gì đó khác nhau. Giá trị của 'at' thay đổi trong một trường hợp và không phải trong trường hợp khác. – EJP

+0

FYI, làm 'at =" tw ";' và sau đó 'tại [2] = '\ 0';' là thừa. '" tw "' tạo ra một chuỗi ký tự là * đã * null-chấm dứt. Bộ nhớ cho chuỗi "" tw "' trông giống như '['t' | 'w' | '\ 0'] '. Không chỉ vậy, nhưng 'at [2] = '\ 0';' cũng sẽ dẫn đến hành vi không xác định; '" tw "' tạo ra một chuỗi ký tự, là một chuỗi * chỉ đọc * mà là * không thể đọc được *, vì vậy việc viết vào chuỗi ký tự chỉ đọc này sẽ gọi hành vi không xác định. Để thực sự gán một cái gì đó theo cách này, bạn phải làm 'const char * at =" tw ";' sẽ tạo ra một chuỗi ký tự và có 'at' trỏ đến cùng một vị trí. – RastaJedi

Trả lời

12

Vì một char* không phải là một chuỗi. Nó chỉ là một con trỏ đến một số nhân vật, với quy ước rằng có thể có nhiều nhân vật để theo dõi và sau khi người cuối cùng có một '\0'.

Chuỗi ký tự trong C (và như vậy trong C++) như "abc" chỉ là một mảng ký tự, với trình biên dịch âm thầm thêm '\0'. Khi bạn gán một mảng cho một con trỏ, mảng âm thầm chuyển đổi một con trỏ thành phần tử đầu tiên. Kết quả là

at = "tw"; 

phương tiện, con trỏ at được gán địa chỉ của ký tự đầu tiên trong chuỗi literal "tw". Bằng cách này, nó sẽ mất giá trị cũ của nó. Vì đây là địa chỉ của mảng ký tự được phân bổ động, bạn đang rò rỉ mảng này.

Khi sau này bạn gán cho một ký tự trong mảng at bây giờ trỏ đến, bạn đang gán một giá trị mới cho một số ký tự trong chuỗi ký tự. Đó là gọi hành vi không xác định và chương trình treo hoặc rơi ngay lập tức có lẽ là tốt nhất có thể xảy ra với bạn khi bạn làm điều này. (Trên nhiều nền tảng bạn đang ghi vào bộ nhớ chỉ đọc.)

Sau đó, bạn chuyển at tới delete[] (và not delete, since you called new[], not new). Khi làm như vậy, bạn chuyển nó tới địa chỉ của chuỗi ký tự, thay vì mảng ký tự được cấp phát. Điều này sẽ, tất nhiên, mess up the heap manager. (Thư viện thời gian chạy của VC bắt được điều này trong chế độ Gỡ lỗi.) Mặt khác, sao chép một ký tự chuỗi theo ký tự từ một mảng này sang mảng khác. Không có con trỏ nào sẽ được thay đổi, chỉ những mẩu bộ nhớ được sao chép. Con trỏ tới mảng đích vẫn trỏ đến mảng đích sau đó, chỉ có dữ liệu trong mảng đó đã thay đổi.

Để tôi thêm điều này: Là người mới bắt đầu trong C++, bạn nên sử dụng std::string, thay vì chuỗi C. Điều đó làm tất cả công việc bẩn thỉu cho bạn và có ngữ nghĩa lành mạnh.

0

Trong ví dụ đầu tiên bạn đang chaning giá trị tại, trong lần thứ hai bạn đang thay đổi giá trị của những gì tại điểm đến. Gán một char * cho một chuỗi trích dẫn kép gán nó cho một con trỏ const tĩnh.

Cụ thể, trong ví dụ đầu tiên lúc này chỉ một vị trí khác trong bộ nhớ.

+0

Trong ví dụ 'const char * s =" hello world; ',' s' là một con trỏ trỏ tới * const char *, không phải là con trỏ * const * thành char. Có rất nhiều lỗi chính tả trong câu trả lời của bạn ... Ngoài ra, tôi giả sử bạn có thể có nghĩa là để nói "chỉ định một chuỗi kép trích dẫn một' char * 'chứ không phải là cách khác xung quanh? Vì bạn không thể gán bất kỳ thứ gì cho một chuỗi ký tự, nghĩa là, một chuỗi ký tự không bao giờ có thể có trên LHS? – RastaJedi

9

Có 3 điều cần hiểu:

1) char *at; chỉ là một biến con trỏ.
Biến con trỏ đơn giản có nghĩa là nó chứa địa chỉ bộ nhớ.

2) new char[3] trả về địa chỉ bắt đầu của bộ nhớ được cấp phát trên heap.

3) "hello" trả về địa chỉ của chuỗi ký tự.

char *at = new char [3]; 
//at now contains the address of the memory allocated on the heap 


at = "hello"; 
//at now contains the address of the static string. 
// (and by the way you just created a 3 byte memory leak) 


delete[] at; 
//WOOPS!!!! you can't do that because you aren't deleting 
// the original 3 chars anymore which were allocated on the heap! 
//Since at contains the string literal's memory address you're 
// trying to delete the string literal. 

Một lưu ý về việc sửa đổi bộ nhớ chỉ đọc:

Ngoài ra, bạn không bao giờ nên được sửa đổi một chuỗi chữ. I E. điều này không bao giờ được thực hiện:

char *at = "hello"; 
at[2] = '\0'; 

Bộ nhớ cho chuỗi ký tự phải được đọc và nếu bạn thay đổi, kết quả không được xác định bằng ngôn ngữ C++.

Vì bạn đang sử dụng C++:

Vì bạn đang sử dụng C++ hãy xem xét việc sử dụng các loại std::string để thay thế.

#include <string> 

using namespace std; 

int main(int argc, char **argv) 
{ 
    string s = "hello"; 
    s += " world!"; 

    //s now contains "hello world!" 

    s = "goodbye!"; 

    //Everything is still valid, and s contains "goodbye!" 


    //No need to cleanup s. 

    return 0; 
} 
5

Đừng quên sử dụng

delete [] 

bất cứ khi nào bạn đang phân bổ một cái gì đó với [].

14

Khi bạn làm

char *at = ...; 

at = "hello"; 

Bạn đang cơ bản ghi đè lên giá trị con trỏ (ví dụ, địa chỉ của bộ nhớ được phân bổ cho bạn bằng cách new[]) với địa chỉ của một chuỗi liên tục tĩnh. Điều này có nghĩa là khi bạn xóa bộ nhớ, bạn đang vượt qua delete một con trỏ không được trả lại trước đây bởi new.

Đó là điều không tốt để làm.

Trong C và C++, việc gán cho con trỏ thường không làm bất cứ điều gì đối với bộ nhớ được chỉ ra, chúng thay đổi chính con trỏ. Điều này có thể gây nhầm lẫn nếu bạn quen với một ngôn ngữ trong đó chuỗi có nhiều "công dân hạng nhất".

Ngoài ra, bạn nên sử dụng delete[] nếu bạn sử dụng new[].

+1

Vì vậy, tôi sẽ được chính xác trong giả sử strcpy (var, "string") vòng qua từng ký tự cá nhân trong "chuỗi" và gán nó vào chỉ số chính xác trong var? – Omar

+0

@Omar: Có, strcpy() sẽ viết một ký tự cùng một lúc, tối đa và bao gồm ký tự kết thúc NIL. – unwind

4

Con trỏ giữ địa chỉ. Toán tử = cho một con trỏ thay đổi địa chỉ được giữ.

at = "tw"; 

Làm cho điểm đến mảng "tw" (mảng được tạo bởi trình biên dịch để giữ ký tự tw), nó không còn trỏ đến mảng bạn đã tạo bằng mới. được tạo trong tệp.

at[2] = '\0'; 

Thêm NULL vào cuối mảng complier.

+0

"' tại [2] = '\ 0'; 'Thêm một NULL vào cuối của mảng biên dịch" - điều này gọi UB là chuỗi ký tự chuỗi (mảng được tạo bởi trình biên dịch mà bạn đang đề cập đến) được đọc- chỉ và không bao giờ được sửa đổi. Nhưng tôi giả sử bạn biết điều đó; Tôi chỉ muốn chỉ ra cho những người khác đọc câu trả lời của bạn. – RastaJedi

0

Trong ví dụ đầu tiên của bạn, bạn đang phân bổ một số bộ nhớ và trỏ đến nó bằng biến "at". Khi bạn làm

at = "tw" 

bạn đang trỏ lại char * vào chuỗi ký tự không đổi. Điều này khiến bạn bị rò rỉ bộ nhớ. Khi bạn tiếp tục xóa "at", bạn đang cố xóa bộ nhớ ngăn xếp.

strcpy đi qua từng ký tự và sao chép giá trị của chúng vào bộ nhớ mới bạn phân bổ. Điều này còn được gọi là bản sao sâu.

0

Trong ví dụ đầu tiên, bạn đã gây ra rò rỉ bộ nhớ.

Biến của bạn at là con trỏ đến địa chỉ bộ nhớ, chứ không phải chính chuỗi đó. Khi bạn gán địa chỉ của "tw" cho con trỏ, bạn đã mất địa chỉ gốc mà bạn đã nhận được với new. at hiện trỏ đến địa chỉ mà bạn không phân bổ với new, vì vậy bạn không thể delete địa chỉ đó.

Nếu bạn nghĩ con trỏ là số nguyên, nó có thể sẽ có ý nghĩa hơn. Tôi đã gán số tùy ý làm địa chỉ vì mục đích thảo luận.

char *at = new char[3]; // 0x1000 
at = "tw";     // 0x2000 
at[2] = '\0';    // set char at 0x2002 to 0 
delete at;     // delete 0x2000 (whoops, didn't allocate that!) 
0

Bạn nhầm lẫn hai điều: làm cho con trỏ trỏ đến thứ gì đó khác biệt (đây là nhiệm vụ nào) và sao chép một số dữ liệu vào một địa điểm được trỏ bằng con trỏ.

at = "tw"; 

mã này tạo thành một chữ "tw" được tạo ở đâu đó trong bộ nhớ chỉ đọc. Cố gắng viết cho nó là một hành vi không xác định.

char *at = new char [3]; 
strcpy(at,"t"); 

mã này cấp phát bộ nhớ cho ba ký tự và làm at điểm cho phần này của bộ nhớ (dòng 1) và sau đó sao chép một số dữ liệu vào bộ nhớ được trỏ bởi at.

Và hãy nhớ, mà nhớ được phân bổ với new[] nên được deallocated với delete[], không delete

tôi lời khuyên bạn tìm hiểu thêm về con trỏ. This discussion bao gồm điều này.

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