2013-05-02 46 views
6

Tôi có thư viện của bên thứ ba đang sử dụng char * (non-const) làm trình giữ chỗ cho các giá trị chuỗi. Cách đúng và an toàn để gán các giá trị cho các kiểu dữ liệu đó là gì? Tôi có benchmark thử nghiệm sau đây có sử dụng lớp hẹn giờ của riêng tôi để đo thời gian thực hiện:Cách thích hợp để xử lý các chuỗi char * là gì?

#include "string.h" 
#include <iostream> 
#include <sj/timer_chrono.hpp> 

using namespace std; 

int main() 
{ 
    sj::timer_chrono sw; 

    int iterations = 1e7; 

    // first method gives compiler warning: 
    // conversion from string literal to 'char *' is deprecated [-Wdeprecated-writable-strings] 
    cout << "creating c-strings unsafe(?) way..." << endl; 
    sw.start(); 
    for (int i = 0; i < iterations; ++i) 
    { 
     char* str = "teststring"; 
    } 
    sw.stop(); 
    cout << sw.elapsed_ns()/(double)iterations << " ns" << endl; 

    cout << "creating c-strings safe(?) way..." << endl; 
    sw.start(); 
    for (int i = 0; i < iterations; ++i) 
    { 
     char* str = new char[strlen("teststr")]; 
     strcpy(str, "teststring"); 
    } 
    sw.stop(); 
    cout << sw.elapsed_ns()/(double)iterations << " ns" << endl; 


    return 0; 

} 

Output:

creating c-strings unsafe(?) way... 
1.9164 ns 
creating c-strings safe(?) way... 
31.7406 ns 

Trong khi "an toàn" cách get của thoát khỏi trình biên dịch cảnh báo nó làm cho mã về 15-20 lần chậm hơn theo điểm chuẩn này (1,9 nano giây mỗi lần lặp lại và 31,7 nano giây mỗi lần lặp). Cách chính xác và những gì là rất nguy hiểm về cách "không được chấp nhận"?

+0

Ai sẽ giải phóng bộ nhớ trong trường hợp an toàn? Thư viện của bên thứ 3 được thiết kế kém, thành thật mà nói. –

+2

Nếu bạn định sao chép vào bộ đệm tạm thời, ít nhất hãy sử dụng 'vector '. –

+1

* Ngoài *: 'new char [strlen (" teststr ") + 1]' để tránh viết NUL char bên ngoài bộ đệm. –

Trả lời

10

C++ giữa các ý kiến ​​rõ ràng:

Một chuỗi bình thường theo nghĩa đen có kiểu “mảng của n const char” (phần 2.14.5.8 trong C++ 11).

Hiệu quả của việc cố gắng sửa đổi một chuỗi chữ là undefined (phần 2.14.5.12 trong C++ 11).

Đối với một chuỗi được biết đến tại thời gian biên dịch, cách an toàn có được một non-const char* là này

char literal[] = "teststring"; 

bạn có thể sau đó một cách an toàn

char* ptr = literal; 

Nếu tại thời gian biên dịch bạn không biết chuỗi nhưng biết chiều dài của nó, bạn có thể sử dụng một mảng:

char str[STR_LENGTH + 1]; 

Nếu bạn không biết len gth thì bạn sẽ cần sử dụng phân bổ động. Hãy chắc chắn rằng bạn deallocate bộ nhớ khi các chuỗi không còn cần thiết.

Điều này sẽ chỉ hoạt động nếu API không sở hữu số char* bạn vượt qua.

Nếu nó cố gắng phân phối các chuỗi nội bộ thì nó nên nói như vậy trong tài liệu và thông báo cho bạn về cách thích hợp để phân bổ các chuỗi. Bạn sẽ cần phải khớp phương thức phân bổ của mình với phương thức được API sử dụng nội bộ.

Các

char literal[] = "test"; 

sẽ tạo ra một địa phương, 5 nhân vật mảng với lưu trữ automatinc (có nghĩa là biến sẽ bị phá hủy khi thực hiện rời khỏi phạm vi trong đó biến được khai báo) và khởi tạo mỗi nhân vật trong mảng với các ký tự 't', 'e', ​​'s', 't' và '\ 0'.

Bạn có thể chỉnh sửa những nhân vật này: literal[2] = 'x';

Nếu bạn viết này:

char* str1 = "test"; 
char* str2 = "test"; 

sau đó, tùy thuộc vào trình biên dịch, str1str2 có thể là cùng một giá trị (ví dụ, điểm đến cùng một chuỗi).

("Cho dù tất cả các chuỗi ký tự là khác biệt (nghĩa là, được lưu trữ trong các đối tượng nonoverlapping) được xác định thực hiện." Trong Phần 2.14.5.12 của tiêu chuẩn C++)

Nó cũng có thể đúng là chúng được lưu trữ trong một phần chỉ đọc của bộ nhớ và do đó bất kỳ cố gắng sửa đổi chuỗi sẽ dẫn đến một ngoại lệ/sụp đổ.

Họ cũng là, trong thực tế của các loại const char* nên dòng này:

char * str = "test";

thực sự loại bỏ const-ness trên chuỗi, đó là lý do tại sao trình biên dịch sẽ đưa ra cảnh báo.

+0

Câu trả lời rất hay! Sự khác nhau giữa {char literal [] = "teststring" là gì; } và {char * literal = "teststring"; }? Trước đây không có bất kỳ cảnh báo trình biên dịch nào ít nhất. Cái thứ hai gán chuỗi (mảng ký tự) thành heap và cái cũ gán nó cho chồng? –

+1

@seb xem câu trả lời cập nhật của tôi – Andrei

+0

Vì vậy, {char literal [] = "test"; } gán mảng ký tự cục bộ (trong ngăn xếp). Nếu tôi làm {char * str = literal; } sau đó nó sẽ cung cấp cho con trỏ một địa chỉ cho mảng char này nằm trong ngăn xếp? Nếu đó là sự thật thì bất cứ khi nào 'chữ' đi ra khỏi phạm vi 'str' điểm đến một không gian không được xác định trong bộ nhớ? –

5

Cách không an toàn là cách đi cho tất cả các chuỗi được biết đến lúc biên dịch.

Cách "an toàn" của bạn rò rỉ bộ nhớ và khá khủng khiếp.

Thông thường bạn sẽ có API C lành mạnh chấp nhận const char *, vì vậy bạn có thể sử dụng một cách an toàn thích hợp trong C++, tức là std::string và phương pháp c_str() của nó.

Nếu C API của bạn giả định quyền sở hữu của chuỗi, "cách an toàn" của bạn có lỗ hổng khác: bạn không thể trộn new[]free(), đi qua bộ nhớ phân bổ sử dụng toán tử C++ new[] đến một API C mà hy vọng sẽ gọi free() trên Nó không được phép. Nếu C API không muốn gọi free() sau này trên chuỗi, bạn nên sử dụng new[] ở phía C++.

Ngoài ra, đây là một hỗn hợp lạ của C++ và C.

+5

Anh ta không thể thực sự sử dụng 'std :: string :: c_str()', bởi vì anh ta nói rằng API muốn một non-const –

+2

Nếu API chỉ được viết kém, đó chính xác là những gì const_cast cho. –

+0

@SebastianRedl Điều đó có thể tạo ra một UB trong chương trình của mình –

4

Dường như bạn có hiểu lầm cơ bản về chuỗi C tại đây.

cout << "creating c-strings unsafe(?) way..." << endl; 
sw.start(); 
for (int i = 0; i < iterations; ++i) 
{ 
    char* str = "teststring"; 
} 

Ở đây, bạn chỉ cần gán con trỏ cho hằng số chuỗi. Trong C và C++, các chuỗi ký tự là loại char[N] và bạn có thể gán một con trỏ cho một mảng chuỗi chữ vì mảng "phân rã". (Tuy nhiên, nó không còn được dùng để gán một con trỏ không const cho một chuỗi ký tự.)

Nhưng gán một con trỏ cho một chuỗi ký tự không thể là những gì bạn muốn làm. API của bạn mong đợi một chuỗi không phải const. Chuỗi ký tự là const.

Cách nào đúng và an toàn để gán giá trị cho [char * strings] đó?

Không có câu trả lời chung cho câu hỏi này. Bất cứ khi nào bạn làm việc với chuỗi C (hoặc con trỏ nói chung), bạn cần phải giải quyết khái niệm về quyền sở hữu . C++ tự động xử lý việc này cho bạn với std::string. Bên trong, std::string sở hữu một con trỏ tới một mảng char*, nhưng nó quản lý bộ nhớ cho bạn để bạn không cần phải quan tâm đến nó. Nhưng khi bạn sử dụng các chuỗi C thô, bạn cần đặt suy nghĩ vào việc quản lý bộ nhớ.

Cách bạn quản lý bộ nhớ tùy thuộc vào những gì bạn đang làm với chương trình của mình. Nếu bạn phân bổ một chuỗi C với new[], thì bạn cần phải deallocate nó với delete[]. Nếu bạn phân bổ nó với malloc, thì bạn phải deallocate nó với free().Một giải pháp tốt để làm việc với C-strings trong C++ là sử dụng một con trỏ thông minh có quyền sở hữu chuỗi C được phân bổ. (Nhưng bạn sẽ cần phải sử dụng một deleter mà deallocates bộ nhớ với delete[]). Hoặc bạn chỉ có thể sử dụng std::vector<char>. Như mọi khi, đừng quên phân bổ phòng cho việc chấm dứt null char.

Ngoài ra, lý do vòng lặp thứ 2 của bạn chậm hơn rất nhiều là vì nó phân bổ bộ nhớ trong mỗi lần lặp, trong khi vòng lặp thứ nhất chỉ định một con trỏ tới chuỗi ký tự được cấp phát tĩnh.

+0

Tôi không biết liệu phương pháp đầu tiên có an toàn không, tôi chỉ nhận được cảnh báo trình biên dịch: chuyển đổi từ chuỗi ký tự thành 'char *' không được chấp nhận [-Wdeprecated-writable-strings] ... Có lý do gì để lo lắng về cảnh báo này không? –

+1

@seb không được chấp nhận vì các chuỗi đó thực sự là mảng 'const char' và thay đổi chúng dẫn đến hành vi không xác định. xem phản ứng của tôi. – Andrei

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