2013-05-02 2 views
6

У меня есть сторонняя библиотека, которая использует char * (не const) в качестве заполнителя для строковых значений. Каков правильный и безопасный способ присвоения значений этим типам данных? У меня есть следующий тест тест, который использует свой собственный класс таймера для измерения времени выполнения:Каков правильный способ обработки символов char *?

#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; 

} 

Выход:

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

В то время как «безопасный» способ прибудет-х избавиться от компилятора предупреждения он делает код о В соответствии с этим эталоном в 15-20 раз медленнее (1,9 наносекунды на итерацию против 31,7 наносекунд на итерацию). Каков правильный путь и что опасно для этого «устаревшего» способа?

+0

Кто собирается освободить память в сейфе? Честно говоря, сторонняя библиотека плохо разработана. –

+2

Если вы собираетесь копировать во временный буфер, по крайней мере используйте 'vector '. –

+1

* Помимо *: 'new char [strlen (" teststr ") + 1]', чтобы избежать записи символа NUL за пределами буфера. –

ответ

10

стандарт C++ ясно:

Обычная строка символов имеет тип «массив п сопзЬ полукокса» (раздел 2.14.5.8 в C++ 11).

и

Эффект попытки изменить строку буквальным не определен (раздел 2.14.5.12 в C++ 11).

Для строки известной во время компиляции, безопасный способ получения non-const char* это

char literal[] = "teststring"; 

вы можете безопасно

char* ptr = literal; 

Если во время компиляции вы не знаете строку но знайте его длину, вы можете использовать массив:

char str[STR_LENGTH + 1]; 

Если вы не знаете len gth, тогда вам нужно будет использовать динамическое распределение. Убедитесь, что вы освободили память, когда строки больше не нужны.

Это будет работать только в том случае, если API не имеет права владеть переданным вами char*.

Если он пытается освободить строки внутри, то он должен сказать это в документации и сообщить вам о правильном способе выделения строк. Вам нужно будет сопоставить метод выделения с тем, который используется внутри API.

char literal[] = "test"; 

будет создать локальный, 5 символов массив хранения automatinc (значение переменной будет разрушена, когда выполнение покидает область действия, в котором объявлен переменная) и инициализировать каждый символ в массиве с символами 't', 'e', ​​'s', 't' и '\ 0'.

Вы можете позже изменить эти символы: literal[2] = 'x';

Если вы пишете это:

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

затем, в зависимости от компилятора, str1 и str2 может быть один и тот же значение (т.е. точка в одна и та же строка).

(«Определены ли все строковые литералы (то есть, хранятся в объектах с неперекрывающимися объектами)» в разделе 2.14.5.12 стандарта C++)

Также может быть правдой, что они хранятся в разделе памяти только для чтения, и поэтому любая попытка изменить строку приведет к исключению/сбою.

Они также, в реальности типа const char* так что эта строка:

символ ул = «тест» *;

фактически отбрасывает константу на строку, поэтому компилятор выдаст предупреждение.

+0

Очень хороший ответ! В чем разница между {char literal [] = "teststring"; } и {char * literal = "teststring"; }? Первый не дает никаких предупреждений компилятора, по крайней мере. Последний присваивает цепочке (массив символов) кучу, а прежний присваивает ее стеку? –

+1

@seb см. Мой обновленный ответ – Andrei

+0

Итак {char literal [] = "test"; } назначает массив символов локально (в стеке). Если я сделаю {char * str = literal; } после этого он даст указателю адрес этого массива символов, который находится в стеке? Если это так, тогда всякий раз, когда «литерал» выходит из области «str», указывает на не определенное пространство в памяти? –

5

Небезопасный путь - это путь для всех строк, которые известны во время компиляции.

Ваш «безопасный» способ утечки памяти и довольно ужасающий.

Обычно вы бы иметь вменяемый C API, который принимает const char *, чтобы вы могли использовать правильный безопасный способ в C++, т.е. std::string и способа его c_str().

Если C API предполагает владение строки, ваш «безопасный путь» есть еще один недостаток: вы не можете смешивать new[] и free(), передавая память, выделенную с помощью оператора C++ new[] к C API, который ожидает вызвать free() на Это не разрешено. Если API C не хочет позже вызывать free() в строке, должно быть хорошо использовать new[] на стороне C++.

Кроме того, это странная смесь C++ и C.

+5

Он не может использовать 'std :: string :: c_str()', потому что он говорит, что API хочет неконстантировать –

+2

Если API просто плохо написан, это именно то, для чего предназначен const_cast. –

+0

@SebastianRedl Это может создать UB в его программе –

4

Вы, кажется, имеют фундаментальное непонимание относительно строк C здесь.

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

Здесь вы просто назначаете указатель на константу строкового литерала. В C и C++ строковые литералы имеют тип char[N], и вы можете назначить указатель на массив строковых литералов из-за массива «decay». (Тем не менее, он устарел, чтобы назначить неконтинентный указатель на строковый литерал.)

Но назначение указателя на строковый литерал не может быть тем, что вы хотите сделать. Ваш API ожидает неконстантную строку. Строковые литералы: const.

Каков правильный и безопасный способ присвоения значений этим [char * strings]?

Общего ответа на этот вопрос нет. Всякий раз, когда вы работаете со строками C (или указателями в целом), вам нужно иметь дело с концепцией собственности. C++ позаботится об этом для вас автоматически с std::string. Внутренне std::string владеет указателем на массив char*, но он управляет памятью для вас, поэтому вам не нужно заботиться об этом. Но когда вы используете необработанные C-строки, вам нужно подумать над управлением памятью.

Как вы управляете памятью, зависит от того, что вы делаете с вашей программой. Если вы назначили C-строку с new[], вам необходимо освободить ее с помощью delete[]. Если вы присвоите его malloc, вы должны освободить его с помощью free().Хорошим решением для работы с C-строками в C++ является использование умного указателя, который берет на себя ответственность за выделенную строку C. (Но вам нужно использовать deleter, который освобождает память delete[]). Или вы можете просто использовать std::vector<char>. Как всегда, не забудьте выделить место для завершения нулевого символа.

Кроме того, причина, по которой ваш второй цикл настолько медленнее, потому что он выделяет память на каждой итерации, тогда как первый цикл просто назначает указатель на статически выделенный строковый литерал.

+0

Я не знаю, является ли первый метод вообще небезопасным, я просто получаю предупреждение о компиляторе: преобразование из строкового литерала в 'char *' устарел [-Wdeprecated-writable-strings] ... Есть ли причина беспокоиться об этом предупреждении? –

+1

@seb устарел, потому что эти строки на самом деле являются массивами 'const char' и их изменение приводит к неопределенному поведению. см. мой ответ. – Andrei