2009-12-31 28 views
31

tôi đang làm một số công việc bảo trì và chạy ngang qua một cái gì đó như sau:Điểm "& s [0]" có trỏ đến các ký tự tiếp giáp trong chuỗi std :: không?

std::string s; 
s.resize(strLength); 
// strLength is a size_t with the length of a C string in it. 

memcpy(&s[0], str, strLength); 

Tôi biết sử dụng & s [0] sẽ được an toàn nếu đó là một std :: vector, nhưng đây là một sử dụng an toàn của std :: string?

+3

Việc sử dụng & s [0] là OK, memcpy() được cho là ít hơn. Tại sao không chỉ đơn giản là làm một bài tập, hoặc sử dụng hàm thành viên assign() của chuỗi? –

+1

@Neil Butterworth, đó là những gì tôi đang tự hỏi mình khi xem mã này ...;) – paxos1977

+0

Khi bạn có được lập trình kinh nghiệm trong C++, bạn sẽ kiềm chế ngày càng nhiều hơn từ việc sử dụng 'memset' và' memcpy' và tìm hiểu Lý luận. Đây là một để thêm vào kinh nghiệm của bạn. –

Trả lời

34

A std :: phân bổ chuỗi không được đảm bảo tiếp giáp theo tiêu chuẩn C++ 98/03, nhưng C++ 11 buộc nó phải là. Trong thực tế, tôi cũng không phải Herb Sutter biết về triển khai không sử dụng bộ nhớ tiếp giáp.

Lưu ý rằng điều &s[0] luôn đảm bảo hoạt động theo tiêu chuẩn C++ 11, ngay cả trong trường hợp chuỗi 0 độ dài. Nó sẽ không được đảm bảo nếu bạn đã làm str.begin() hoặc &*str.begin(), nhưng đối với &s[0] tiêu chuẩn định nghĩa operator[] như:

Returns: *(begin() + pos) nếu pos < size(), nếu không một tham chiếu đến một đối tượng kiểu T với giá trị charT(); giá trị tham chiếu sẽ không được sửa đổi

Tiếp tục trên, data() được định nghĩa là:

Returns: Một con trỏ pp + i == &operator[](i) cho mỗi i trong [0,size()].

(chú ý dấu ngoặc vuông ở cả hai đầu của dãy núi này)


Thông báo: pre-tiêu chuẩn C++ 0x không đảm bảo &s[0] để làm việc với chuỗi zero-length (trên thực tế, nó đã được xác định rõ ràng hành vi), và một phiên bản cũ của câu trả lời này giải thích điều này; điều này đã được sửa trong các bản nháp tiêu chuẩn sau này, vì vậy câu trả lời đã được cập nhật cho phù hợp.

+0

Tôi đã không tuân thủ tiêu chuẩn trong vài tháng qua, nhưng đó là ấn tượng của tôi là điều này vẫn còn trong bản nháp 0x, và chưa thực sự yêu cầu (hoặc sẽ là nếu thư viện chọn chỉ thực hiện '03). –

+3

Sutter nói trong một bình luận cho bài viết đó, "ISO C++ hiện tại yêu cầu & str [0] để ho lên một con trỏ tới dữ liệu chuỗi liền kề (nhưng không nhất thiết là null-chấm dứt!)," Mà trong thực tế sẽ làm cho việc sử dụng của OP chính xác. Tuy nhiên, tôi không thể tìm thấy bất cứ điều gì mà nói rằng trong tiêu chuẩn (ít nhất nó không phải trong 21.3.4 lib.string.access). –

+0

Tôi nghĩ điều đó có thể đúng; sai số std 530 cho biết toán tử [] là tiếp giáp nhưng giao diện của trình vòng lặp không được đảm bảo, và báo giá là 23.4.4. Tôi đang đào tiêu chuẩn của mình để kiểm tra. –

6

Về mặt kỹ thuật, không, kể từ std::string không bắt buộc phải lưu trữ nội dung của nó liền kề trong bộ nhớ.

Tuy nhiên, trong hầu hết mọi triển khai (mọi triển khai mà tôi biết), nội dung được lưu trữ liên tục và điều này sẽ "hoạt động".

+0

Bạn có thể xác định một số triển khai mà nó sẽ không hoạt động? –

+2

Không. Nhưng bạn có thể thực hiện như vậy nếu bạn muốn. –

+0

@Neil: Bạn có liên kết/tham chiếu đến TC đó không? –

2

Người đọc cần lưu ý rằng câu hỏi này được hỏi trong năm 2009, khi tiêu chuẩn C++ 03 là ấn bản hiện tại. Câu trả lời này dựa trên phiên bản Tiêu chuẩn đó, trong đó std::string s là không phải được đảm bảo sử dụng bộ nhớ liền kề. Vì câu hỏi này không được hỏi trong bối cảnh của một nền tảng cụ thể (như gcc), tôi không đưa ra giả định nào về nền tảng của OP - đặc biệt là thời tiết hay không sử dụng bộ nhớ phi thường cho số string.

Pháp lý? Co le không. An toàn? Có lẽ, nhưng có lẽ không. Mã tốt? Chà, đừng đi đến đó ...

Tại sao không chỉ làm:

std::string s = str; 

... hoặc:

std::string s(str); 

... hoặc:

std::string s; 
std::copy(&str[0], &str[strLen], std::back_inserter(s)); 

... hoặc:

std::string s; 
s.assign(str, strLen); 

?

+0

hoặc s.assign (str, strLen); –

+0

tốt, được cập nhật w/gán –

+1

'std :: string s (str, strLen);' (dạng ngắn nhất giống hệt nhau, trong trường hợp nhúng null hoặc thiếu null chấm dứt, với hành vi ban đầu từ câu hỏi.) –

0

Điều này thường là không phải an toàn, bất kể chuỗi chuỗi nội bộ có được lưu trữ trong bộ nhớ liên tục hay không. Có thể có nhiều chi tiết triển khai khác liên quan đến cách chuỗi được kiểm soát được lưu trữ bởi đối tượng std::string, bên cạnh tính liên tục.

Một vấn đề thực tế thực tế với điều đó có thể là như sau. Trình tự được kiểm soát của std::string không bắt buộc phải được lưu trữ dưới dạng chuỗi không bị chấm dứt. Tuy nhiên, trong thực tế, việc triển khai nhiều (nhiều nhất) chọn quá cỡ bộ đệm bên trong 1 và lưu trữ chuỗi dưới dạng chuỗi đã kết thúc bằng 0 vì nó đơn giản hóa việc thực hiện phương thức c_str(): chỉ cần trả về con trỏ tới bộ đệm trong và bạn làm xong.

Mã bạn trích dẫn trong câu hỏi của bạn không thực hiện bất kỳ nỗ lực nào để không chấm dứt dữ liệu được sao chép vào bộ đệm trong. Rất có thể nó chỉ đơn giản là không biết liệu chấm dứt 0 là cần thiết trong việc thực hiện này của std::string. Rất có thể nó dựa vào bộ đệm bên trong được lấp đầy bằng số không sau khi gọi tới resize, vì vậy ký tự bổ sung được phân bổ cho zero-terminator bằng cách thực hiện thuận tiện được đặt trước là 0. Tất cả điều này là chi tiết thực hiện, có nghĩa là kỹ thuật này phụ thuộc vào một số giả định khá mỏng manh.

Nói cách khác, trong một số triển khai, bạn có thể phải sử dụng strcpy, không phải memcpy để buộc dữ liệu vào chuỗi được kiểm soát như vậy. Trong khi ở một số triển khai khác, bạn phải sử dụng memcpy chứ không phải strcpy.

+1

Sau khi gọi đến 'thay đổi kích thước', bạn có thể khá chắc chắn rằng chuỗi nội bộ là hoặc không được kết thúc bằng null khi yêu cầu thực hiện. Sau khi một cuộc gọi để 'thay đổi kích thước' sau khi tất cả các bạn phải có một chuỗi hợp lệ của n ký tự (đệm với số không ký tự khi cần thiết). - Tuy nhiên, nó cho thấy sự thiếu hiểu biết về lớp 'std :: string': memcpy được sử dụng hoặc thiếu hiểu biết hoặc là một nỗ lực sai lầm cho hiệu suất (vì lệnh gọi' resize' sẽ kết thúc việc gán giá trị cho bộ đệm hai lần). – UncleBens

+0

@UncleBens: Tôi không hiểu câu đầu tiên của bạn. Trong bất kỳ trường hợp nào, có, tiêu chuẩn ngôn ngữ đảm bảo rằng các cuộc gọi kích thước 'resize' tăng kích thước các chuỗi với số không. Tuy nhiên, tiêu chuẩn đảm bảo chỉ đệm lên đến kích thước được yêu cầu ('strLength' trong trường hợp này), nhưng không có sự bảo đảm nào trong tiêu chuẩn cho ký tự thừa đó, nếu việc triển khai thực hiện một. – AnT

0

Mã có thể hoạt động nhưng may mắn hơn phán đoán, nó đưa ra các giả định về việc triển khai không được đảm bảo. Tôi đề nghị xác định tính hợp lệ của mã này là không thích hợp trong khi nó là một vô nghĩa so với biến chứng đó có thể dễ dàng giảm xuống chỉ còn:

std::string s(str) ; 

hoặc nếu gán cho một đối tượng std :: string hiện, chỉ cần:

s = str ; 

và sau đó để std :: chuỗi tự xác định cách để đạt được kết quả. Nếu bạn đang đi đến khu nghỉ mát này loại vô nghĩa, sau đó bạn có thể cũng không được sử dụng std :: string và dính vào kể từ khi bạn đang giới thiệu lại tất cả những mối nguy hiểm liên quan đến chuỗi C.

+0

Tôi thực sự không thể chắc chắn chuỗi được gán là null chấm dứt. Vì vậy, tốt nhất tôi có thể làm có lẽ sẽ là s.assign (ptr, ptrLength); đó vẫn là một cải tiến mà tôi nghĩ. – paxos1977

+0

Sử dụng biểu mẫu hàm tạo: 'std :: string s (str, strLen);' – GManNickG

6

Có thể sử dụng an toàn. Tôi nghĩ rằng hầu hết các câu trả lời đều đúng, nhưng tiêu chuẩn đã thay đổi. Trích dẫn từ tiêu chuẩn C++ 11, basic_string yêu cầu chung [string.require], 21.4.1.5, nói:

Đối tượng char giống như trong một đối tượng basic_string phải được lưu trữ liên tục kế nhau.Tức là, đối với bất kỳ đối tượng basic_string nào, số nhận dạng & * (s.begin() + n) == & * s.begin() + n sẽ giữ tất cả các giá trị của n sao cho 0 < = n < s .kích thước().

Một chút trước đó, nó nói rằng tất cả các trình vòng lặp là trình vòng lặp truy cập ngẫu nhiên. Cả hai bit đều hỗ trợ việc sử dụng câu hỏi của bạn. (Ngoài ra, Stroustrup dường như sử dụng nó trong cuốn sách mới nhất của mình;))

Không có khả năng thay đổi này được thực hiện trong C++ 11. Tôi dường như nhớ rằng cùng một đảm bảo đã được thêm vào sau đó cho vector, mà cũng có rất hữu ích dữ liệu() con trỏ với bản phát hành đó.

Hy vọng điều đó sẽ hữu ích.

+2

Câu hỏi là pre-C++ 11 (được gắn thẻ như vậy). Bạn đúng, C++ 11 đã làm cho nó chính thức an toàn để làm điều này. – paxos1977

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