Я пытаюсь создать программу на C++, которая печатает текст на терминале разными цветами.
У меня есть следующий код, в котором используется класс Color
, используемый внутри структуры letter_template
, который является элементами массива размером 100:
#include <ctype.h>
#include <stdio.h>
#include <unistd.h>
// Works on little endian processors which mine happens to be
#define COLOR_PREPEND 0x001b;
class Color
{
private:
char contents[6];
public:
Color() {
short prepend = COLOR_PREPEND;
sprintf(contents, "%s[%dm", (char *)prepend, 0);
}
Color(int code) {
short prepend = COLOR_PREPEND;
sprintf(contents, "%s[%dm", (char *)prepend, code);
}
};
typedef struct {
Color *set;
char c;
Color *reset;
} letter_template;
letter_template essay[100];
int main() {
// Initialize array
for (int i = 0; i < sizeof(essay)/sizeof(essay[0]); i++) {
essay[i].set = new Color(); essay[i].reset = new Color();
}
return 0;
}
Сегфолт происходит на первой итерации цикла for
. Используя отладчик, я заметил, что содержимое каждого члена essay
имело нулевые значения (0x0) как для атрибутов set
, так и для reset
, поэтому я думаю, что он не мог инициализировать объекты Color
. Хотя я не могу понять, почему.
Сначала я действительно пытался получить одну длинную непрерывную строку данных в порядке color sequence > letter > color sequence > repeat
. Я знаю, что то, что я закодировал, будет не совсем таким, однако я не понимаю, по крайней мере, почему это не должно работать.
Я в тупике, потому что мое определение класса Color
, как вы видите, работало нормально до того, как я поместил его в структуру класса и typedef (например, оператор sprintf
, как вы видите, работал нормально, когда я только что написал его в main
) ). Мой атрибут contents
имеет 6 байт, и я знаю, что этого достаточно для того, что я там sprintf
нахожу.
Обновлено:
Я обновил код до того, что у меня есть, включая недостающие определения и включения. Моя команда компиляции была:
g++ -c main.cpp -o main.o; g++ main.o -o main.exe
Та же проблема, что и раньше.
Примечание. Изначально это был код C, поэтому он выглядит как код C. Причина, по которой я использую объекты и вещи C++, заключается в том, что мне нужна была утилита, позволяющая устанавливать и сбрасывать определенные цвета для определенных букв без взлома везде жестко закодированных строк, для чего я хотел использовать классы и методы.
Будет ли sprintf(contents, "%s[%dm", (char *)prepend, 0)
никогда не создавать строку длиной более пяти символов? Почему вы здесь используете sprintf
? Во всяком случае, вам следует использовать snprintf
, чтобы избежать переполнения буфера. Или еще лучше используйте std::string
для всех строк.
Мой атрибут «contents» имеет 6 байт, и я знаю, что этого достаточно для того, что я там делаю. -- Если длина строки превышает 6 символов, вы не договорились с компилятором об уменьшении размера строки. Компьютерное программирование не работает на основе слепого доверия — вы должны написать код, чтобы гарантировать, что строка подойдет.
Кроме того, (char *)prepend
? Что? Почему? Что ты пытаешься сделать? Почему prepend
это short
? Почему вам нужно явно преобразовать значение short
в char *
? И что вообще такое COLOR_PREPEND
? Пожалуйста, создайте правильный минимально воспроизводимый пример , чтобы показать нам.
пожалуйста, не удаляйте использование объявлений и включений из кода. Они являются частью кода, без них код не компилируется, добавление разных потенциально меняет смысл кода
Кроме того, не используйте указатели для объектов Color
. В этом просто нет необходимости. Я думаю, какой бы ресурс вы ни использовали для изучения основ C++, он не принесет вам хороших результатов. Он учит вас большему C, чем C++. Если вы серьезно настроены изучать C++, купите несколько хороших книг.
Кстати, проблема не в массиве, а в операторах sprintf. Мы не можем их скомпилировать. Удалив строки, которые не компилируются, ваш код компилируется и запускается без проблем, массив заполняется godbolt.org/z/YjPT7659x.
Мой атрибут «contents» имеет 6 байт, и я знаю, что этого достаточно для того, что я там делаю. -- Кроме того, вы могли бы просто попробовать небольшой эксперимент самостоятельно и увеличить размер буфера до 10, 100 или чего угодно, но намного больше, чем 6 символов. Если бы затем вы увидели, что сбоя не произошло, то вы бы (или должны были) провести немного больше исследований, чтобы выяснить, превышают ли эти строки буфер длиной всего 6 символов. Тогда, вполне возможно, не нужно было бы публиковать весь этот вопрос, поскольку вы бы обнаружили ошибку.
когда ваш код имеет неопределенное поведение (например, из-за записи за пределы некоторого буфера памяти), может показаться, что он работает какое-то время, пока это не произойдет. Тот факт, что некоторый код компилируется и выдает ожидаемый результат, не означает, что он правильный, что он не может быть причиной сбоев где-то в дальнейшем.
Это происходит из-за сбоя, потому что ваш код пытается получить доступ к неверному адресу 0x001b.
Не редактируйте свой вопрос, говоря «решено, исправлено, спасибо» или что-то подобное. Вместо этого вы можете принять ответ, полученный ниже.
Без ссылки: COLOR_PREPEND
— плохое имя. ESC
было бы лучше. Для этого вам также не нужен макрос. Просто inline constexpr char ESC = 0x0b;
.
Также не связано: первый конструктор (по умолчанию) можно упростить, делегировав работу второму конструктору: Color() : Color(0) {}
«класс Color
вложен в typedef letter_template
» — вложенность имеет особое значение одного класса, определенного внутри другого, struct letter_template { class Color {}; };
. Ваш код не демонстрирует вложенность. Кроме того, typedef
не так точен, как struct
, поскольку typedef
не требует определения псевдонима для определяемого пользователем типа.
«Используя отладчик, я заметил, что содержимое каждого члена essay
имеет нулевые значения (0x0) как для атрибутов set
, так и для reset
, поэтому я думаю, что он не смог инициализировать объекты Color
». -- Это хорошо и должно привести вас к попытке упростить пример кода. Это предполагает, что ни один объект не был инициализирован, поэтому попробуйте удалить массив. Каждый уровень отвлечения, который вы устраняете, облегчает понимание того, что не так.
Ваш код демонстрирует неопределенное поведение, поэтому возможно все, и все ставки сделаны.
В ваших вызовах sprintf()
спецификатор %s
ожидает указатель char*
на символьную строку, завершающуюся нулем, но вместо этого вы передаете ему указатель char*
, который был приведен к типу из целочисленного значения short
. Указатель не указывает на действительную память, из которой sprintf
можно прочитать, поэтому происходит сбой.
Вероятно, вы хотели ввести адрес short
, а не его значение, например:
class Color
{
private:
char contents[6];
public:
Color() {
setCode(0);
}
Color(int code) {
setCode(code);
}
void setCode(int code) {
short prepend = COLOR_PREPEND;
sprintf(contents, "%s[%dm", (char *)&prepend, code);
};
В системе с прямым порядком байтов это могло бы «сработать» для вставки 0x1B
байта в ваш contents
буфер. Но это просто неправильный подход, и с самого начала так поступать не следует. Для того, что вы пытаетесь сделать, используйте %c
вместо %s
, например:
static const char COLOR_PREPEND = 0x1b;
class Color
{
private:
char contents[6];
public:
Color() {
setCode(0);
}
Color(int code) {
setCode(code);
}
void setCode(int code) {
sprintf(contents, "%c[%dm", COLOR_PREPEND, code);
};
Или вы можете просто жестко запрограммировать char
прямо в строке формата:
class Color
{
private:
char contents[6];
public:
Color() {
setCode(0);
}
Color(int code) {
setCode(code);
}
void setCode(int code) {
sprintf(contents, "\x1b[%dm", code);
};
В любом случае, обратите внимание, что у вас все еще есть потенциальное переполнение буфера, если code
когда-либо содержит значение < -9
или > 99
.
По крайней мере, вам следует принять code
как (unsigned) char
вместо int
и выполнить правильную проверку его значения. В противном случае увеличьте размер contents
, чтобы он соответствовал самому большому int
, который вы когда-либо использовали.
Или просто замените contents
на std::string
, например:
static const char COLOR_PREPEND = 0x1B;
class Color
{
private:
std::string contents;
public:
Color() {
setCode(0);
}
Color(int code) {
setCode(code);
}
void setCode(int code) {
contents = std::string(1, COLOR_PREPEND) + "[" + std::to_string(code) + "m";
// or:
// contents = "\x1b[" + std::to_string(code) + "m";
}
};
Проблема с этим кодом заключается в этой строке:
sprintf(contents, "%s[%dm", (char *)prepend, 0);
Конкретно в выражении (char *)prepend
.
В этом выражении вы просите компилятор интерпретировать prepend
как указатель на некоторый буфер памяти с элементами char
в нем.
Поскольку prepend
— это число, вместо того, чтобы вызывать ошибку во время компиляции, оно преобразуется в фактический адрес где-то в памяти, согласно примеру адрес — 0x001b
.
А затем, когда вы звоните sprintf
, компьютер переходит по этому адресу и пытается прочитать оттуда char
.
Поэтому вы получаете сегфолт.
Операцию приведения, которую вы используете, необходимо использовать с указателем на память, которую вы хотите начать интерпретировать как char
.
Поэтому, чтобы код заработал, нужно использовать (char *)&prepend
.
Теперь он не будет иметь доступа к неинициализированной памяти.
Но сделает ли он то, что вы хотите, или нет, я не знаю, так как не очень понимаю применение этого приведения в этом коде.
извините за придирки, но код имеет неопределенное поведение, то, что вы описываете: «... компьютер идет по этому адресу и пытается прочитать символы оттуда...» - это то, что может произойти, но при выполнении скомпилированного кода может произойти и что-нибудь еще. исполняемый файл.
typedef struct { .... } letter_templates;
хочет бытьstruct letter_templates {....};
, если только вы не пишете код на C