Попытка инициализировать объекты класса внутри структуры приводит к ошибкам сегментации

Я пытаюсь создать программу на 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++, заключается в том, что мне нужна была утилита, позволяющая устанавливать и сбрасывать определенные цвета для определенных букв без взлома везде жестко закодированных строк, для чего я хотел использовать классы и методы.

typedef struct { .... } letter_templates; хочет быть struct letter_templates {....};, если только вы не пишете код на C
463035818_is_not_an_ai 24.06.2024 22:48

Будет ли sprintf(contents, "%s[%dm", (char *)prepend, 0) никогда не создавать строку длиной более пяти символов? Почему вы здесь используете sprintf? Во всяком случае, вам следует использовать snprintf, чтобы избежать переполнения буфера. Или еще лучше используйте std::string для всех строк.

Some programmer dude 24.06.2024 22:48

Мой атрибут «contents» имеет 6 байт, и я знаю, что этого достаточно для того, что я там делаю. -- Если длина строки превышает 6 символов, вы не договорились с компилятором об уменьшении размера строки. Компьютерное программирование не работает на основе слепого доверия — вы должны написать код, чтобы гарантировать, что строка подойдет.

PaulMcKenzie 24.06.2024 22:50

Кроме того, (char *)prepend? Что? Почему? Что ты пытаешься сделать? Почему prepend это short? Почему вам нужно явно преобразовать значение short в char *? И что вообще такое COLOR_PREPEND? Пожалуйста, создайте правильный минимально воспроизводимый пример , чтобы показать нам.

Some programmer dude 24.06.2024 22:50

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

463035818_is_not_an_ai 24.06.2024 22:50

Кроме того, не используйте указатели для объектов Color. В этом просто нет необходимости. Я думаю, какой бы ресурс вы ни использовали для изучения основ C++, он не принесет вам хороших результатов. Он учит вас большему C, чем C++. Если вы серьезно настроены изучать C++, купите несколько хороших книг.

Some programmer dude 24.06.2024 22:52

Кстати, проблема не в массиве, а в операторах sprintf. Мы не можем их скомпилировать. Удалив строки, которые не компилируются, ваш код компилируется и запускается без проблем, массив заполняется godbolt.org/z/YjPT7659x.

463035818_is_not_an_ai 24.06.2024 22:54

Мой атрибут «contents» имеет 6 байт, и я знаю, что этого достаточно для того, что я там делаю. -- Кроме того, вы могли бы просто попробовать небольшой эксперимент самостоятельно и увеличить размер буфера до 10, 100 или чего угодно, но намного больше, чем 6 символов. Если бы затем вы увидели, что сбоя не произошло, то вы бы (или должны были) провести немного больше исследований, чтобы выяснить, превышают ли эти строки буфер длиной всего 6 символов. Тогда, вполне возможно, не нужно было бы публиковать весь этот вопрос, поскольку вы бы обнаружили ошибку.

PaulMcKenzie 24.06.2024 22:57

когда ваш код имеет неопределенное поведение (например, из-за записи за пределы некоторого буфера памяти), может показаться, что он работает какое-то время, пока это не произойдет. Тот факт, что некоторый код компилируется и выдает ожидаемый результат, не означает, что он правильный, что он не может быть причиной сбоев где-то в дальнейшем.

463035818_is_not_an_ai 24.06.2024 23:02

Это происходит из-за сбоя, потому что ваш код пытается получить доступ к неверному адресу 0x001b.

Gene 24.06.2024 23:16

Не редактируйте свой вопрос, говоря «решено, исправлено, спасибо» или что-то подобное. Вместо этого вы можете принять ответ, полученный ниже.

Ted Lyngmo 24.06.2024 23:21

Без ссылки: COLOR_PREPEND — плохое имя. ESC было бы лучше. Для этого вам также не нужен макрос. Просто inline constexpr char ESC = 0x0b;.

Ted Lyngmo 24.06.2024 23:21

Также не связано: первый конструктор (по умолчанию) можно упростить, делегировав работу второму конструктору: Color() : Color(0) {}

Ted Lyngmo 24.06.2024 23:28

«класс Color вложен в typedef letter_template» — вложенность имеет особое значение одного класса, определенного внутри другого, struct letter_template { class Color {}; };. Ваш код не демонстрирует вложенность. Кроме того, typedef не так точен, как struct, поскольку typedef не требует определения псевдонима для определяемого пользователем типа.

JaMiT 25.06.2024 03:16

«Используя отладчик, я заметил, что содержимое каждого члена essay имеет нулевые значения (0x0) как для атрибутов set, так и для reset, поэтому я думаю, что он не смог инициализировать объекты Color». -- Это хорошо и должно привести вас к попытке упростить пример кода. Это предполагает, что ни один объект не был инициализирован, поэтому попробуйте удалить массив. Каждый уровень отвлечения, который вы устраняете, облегчает понимание того, что не так.

JaMiT 25.06.2024 03:22
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
15
82
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Ваш код демонстрирует неопределенное поведение, поэтому возможно все, и все ставки сделаны.

В ваших вызовах 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. Теперь он не будет иметь доступа к неинициализированной памяти. Но сделает ли он то, что вы хотите, или нет, я не знаю, так как не очень понимаю применение этого приведения в этом коде.

извините за придирки, но код имеет неопределенное поведение, то, что вы описываете: «... компьютер идет по этому адресу и пытается прочитать символы оттуда...» - это то, что может произойти, но при выполнении скомпилированного кода может произойти и что-нибудь еще. исполняемый файл.

463035818_is_not_an_ai 25.06.2024 09:05

Другие вопросы по теме