2016-03-14 2 views
7

Я использую библиотеку C в C++ и написал обертку. В какой-то момент мне нужно преобразовать std::string в строку c-style. Существует класс с функцией, которая возвращает строку. Выполнение возвращенной строки выполняется, если строка короткая, в противном случае - нет. Вот простой и уменьшенный пример, иллюстрирующий проблему:Кастинг c_str() работает только для коротких строк

#include <iostream> 
#include <string> 

class StringBox { 
public: 
    std::string getString() const { return text_; } 

    StringBox(std::string text) : text_(text){}; 

private: 
    std::string text_; 
}; 

int main(int argc, char **argv) { 
    const unsigned char *castString = NULL; 
    std::string someString = "I am a loooooooooooooooooong string"; // Won't work 
    // std::string someString = "hello"; // This one works 

    StringBox box(someString); 

    castString = (const unsigned char *)box.getString().c_str(); 
    std::cout << "castString: " << castString << std::endl; 

    return 0; 
} 

Отработка файл выше принтами на консоль:

castString:

а если я своп комментируя someString , он правильно печатает

castString: привет

Как это возможно?

ответ

16

Вы вызываете c_str на объект временной строки, перенастроенный функцией-членом getString(). Указатель, возвращаемый c_str(), действителен только в том случае, если существует исходный строковый объект, поэтому в конце строки, где вы назначаете castString, он оказывается свисающим указателем. Официально это приводит к неопределенному поведению.

Так почему же это работает для коротких строк? Я подозреваю, что вы видите эффекты Short String Optimization, оптимизация, где для строк меньше определенной длины символьные данные хранятся внутри байтов самого строкового объекта, а не в куче. Возможно, что временная строка, которая была возвращена, была сохранена в стеке, поэтому, когда она была очищена, никаких деаллокодирования не произошло, и указатель на объект с истекшим строкой все еще хранит ваши старые байты строки. Это похоже на то, что вы видите, но это все еще не значит, что вы делаете, это хорошая идея. :-)

+0

Спасибо, это имеет смысл! Самое смешное, что код работал отлично в течение нескольких месяцев в моей другой системе. Только теперь, когда я обновил Ubuntu с 14.04 до 15.10 и переустановил все, что он изменил. Я предполагаю, что это неопределенное поведение ... – Cat

+0

И спасибо за разработку. Можете ли вы объяснить, что вы подразумеваете под «не очень хорошей идеей»? Или вы имеете в виду мою ошибку при вызове c_str() объекта, возвращаемого функцией? – Cat

+0

Я думаю, что легкое исправление: 'std :: cout <<" castString: "<< (const unsigned char *) box.getString(). C_str() << std :: endl;' – chqrlie

6

box.getString() является обход анонимный.. c_str() действителен только для длины переменной.

Так что в вашем случае c_str() является признана недействительной к тому времени, когда Вы добираетесь до std::cout. Поведение чтения содержимого указателя: undefined.

(Интересно, что поведение вашей короткой строки является возможно отличается из-за std::string хранения коротких строк по-другому.)

+1

Я бы с/длина/жизнь – NathanOliver

+0

Спасибо за помощь, я понимаю сейчас! – Cat

5

Как вернуться к значению

box.getString() является временным и так

box.getString().c_str() действителен только во время выражения, то это оборванный указатель.

Вы можете исправить это с

const std::string& getString() const { return text_; } 
+0

Спасибо за вашу помощь! – Cat

+0

Чтобы это было ясно: это устраняет проблему, потому что поскольку возвращаемое значение 'getString()' теперь является ссылкой на '_text', хранящимся в' StringBox' (а не анонимным временным, как в вашем коде), результат ' .c_str() 'действителен для продолжительности жизни' _text' или любой другой операции модификации на '_text'. –

5

box.getString() производит временное. Вызов c_str(), который дает вам указатель на временный. После временного прекращения существования, которое немедленно, указатель недействителен, болтается указатель.

Использование оборванного указателя - неопределенное поведение.

4

Прежде всего, ваш код UB зависит от длины строки: В конце

castString = (const unsigned char *)box.getString().c_str(); 

строка, возвращаемая getString разрушается и castString является оборванным указателем на внутренний буфер уничтоженный строковый объект.

Причина, по которой ваш код «работает» для небольших строк, вероятно, малая оптимизация строк: короткие строки (обычно) сохраняются в самом строковом объекте, а не сохраняются в динамически распределенном массиве, и, по-видимому, эта память по-прежнему доступна и немодифицированный в вашем случае.

Смежные вопросы