Так вот, я начал писать класс для реализации строки C++, хотя использую я методики из C, впрочем сейчас это неважно. Как и обычный класс string из STL основная задача данной приблуды - динамическое выделение памяти под строку.
Я начал писать это чудо, но уже на стадии реализации конструктора класса наткнулся на некую аномалию, она возникает не всегда, но это не меняет ситуацию. Иногда когда я преобразую мой блок для хранения данных в const char* с помощью стандартного type-cast я получаю некую аномалию в конце строки. Стоит подметить что я заметил её когда выводил данные с помощью <iostream> библиотеки.
Мой блок для хранения данных типа uint8_t* или unsigned char*. Впрочем мне понятно как решить данную проблему, нужно просто записать символ окончания строки \0 на конечный адрес блока. Но всё же мне интересно, это обязательно делать, или я что-то сделал не так и теперь из-за этого страдаю?
class string {
private:
uint8_t* m_data = 0;
size_t m_length = 0;
public:
string();
string(char _var);
string(char* _var);
string(const char* _var);
string(const string& _var);
const char* to_str() const;
size_t length() const;
~string();
};
string::string() {
m_data = (uint8_t*)malloc(0);
}
string::string(char _var) {
m_length++;
m_data = (uint8_t*)malloc(m_length);
memcpy_s(m_data, m_length, (void*)_var, m_length);
}
string::string(char *_var) {
m_length = strlen(_var);
m_data = (uint8_t*)malloc(m_length);
memcpy_s(m_data, m_length, (uint8_t*)_var, m_length);
}
string::string(const char* _var) {
m_length = strlen(_var);
m_data = (uint8_t*)malloc(m_length);
memcpy_s(m_data, m_length, (uint8_t*)_var, m_length);
}
string::string(const string& _var) {
m_length = _var.length();
m_data = (uint8_t*)malloc(m_length);
memcpy_s(m_data, m_length, (uint8_t*)_var.to_str(), m_length);
}
const char* string::to_str() const {
return (const char*)m_data;
}
size_t string::length() const {
return m_length;
}
string::~string() {
free(m_data);
}
Данных на этот счёт в интернете я на удивление не нашёл, хотя возможно я просто не правильно задал вопрос.
В первом конструкторе string(char) вы выделяете память под ровно один символ (кстати m_length++ плохая идея, лучше m_length = 1).
Во втором конструкторе string(char*) вы измеряете размер строки при помощи функции strlen, которая принимает «строку в стиле Си» и возвращает размер с учетом терминального нуля. ASCII строка из одного символа всегда будет иметь размер 2.
string('a').length() == 1;
string("a").length() == 2;
Но эти все конструкторы создают нормальную (в широком понимании) копию строки во всех случаях. Ошибка находится в функции to_str(), которая возвращает указатель на константный char, что стандартной библиотекой воспринимается как строка в стиле Си и, соответственно, должна иметь терминальный ноль. Что имеет смысл, ведь указатель на память не несёт информации о том, сколько этой памяти доступно.
Здесь нужно сделать выбор: либо хранить всегда терминальный ноль в строке, либо всегда его убирать. Если его не хранить, то единственная выгода - экономия одного байта. А при конвертировании в строку в стиле Си придётся выделять память, дописывать ноль, а потом ещё и удалять её. А если хранить, то нужно лишь в нескольких местах обработать этот случай.
Есть ещё третий вариант – убрать из интерфейса конвертацию в строку в стиле Си, что сильно сузит область применения этой библиотеки.
Сборка персонального компьютера от Artline: умный выбор для современных пользователей