У меня есть проект ESP32, в котором есть класс с именем Card
, который представляет тег NFC. В этом классе 3 поля, id, значение и time_saved. Класс также имеет функцию сохранения тега в базе данных, хранящейся на SD. Когда модуль читает карту, он возвращает ее uid. Затем он создает указатель на Card
. Затем он ищет uid в базе данных. Если ее нет, программа спрашивает, хочет ли пользователь сохранить карту. Когда они говорят «да» и вводят сообщение, программа использует функцию Card
save_card
. Вот в чем проблема.
Внутри функции класса save_card
и только в этой функции id карты внезапно преобразуется в B
. Он переходит от строкового представления длиной 6 байтов к B
. И это еще не все. Если тег имеет 4-байтовый uid, этой проблемы не возникает.
Я собираюсь показать код для лучшего понимания, а затем объясню, что я сделал, чтобы попытаться найти корень проблемы.
void loop() {
// Get RFID tag
string tag = read_RFID(&rfid);
if (tag.empty()) return;
// Query database
Card *card;
try {
card = Card(tag).select_card();
} catch (const runtime_error &e) {
Serial.println(e.what());
return;
}
// If the operation switch is high, enter edition mode
if (digitalRead(operation_pin) == HIGH) {
edition_mode(card);
return;
}
// If the operation switch is low, enter playback mode
playback_mode(card);
return;
}
void edition_mode(Card *card) {
// Check if the card is saved in the database
if (!card->get_value().empty()) {
Serial.println("\nCard already saved");
card->print();
return;
}
Serial.println("Tag not found\n");
Serial.println("Do you want to save the tag? (y/n)");
// Wait for the user to enter a character
while (Serial.available() == 0) delay(100);
char c = Serial.read();
// If the user doesn't want to save the card, return
if (c != 'y') {
Serial.println("Tag not saved");
return;
};
// Ask the user for a message
Serial.println("Enter a message to save to the tag:");
string message = "";
while (Serial.available() == 0) delay(100);
while (Serial.available() > 0) message += (char)Serial.read();
// Check if the message is empty
if (message.empty()) {
Serial.println("Message cannot be empty");
return;
}
// Save the message to the tag
try {
card->save_card(message, format_now(rtc.now()));
} catch (const runtime_error &e) {
Serial.println(e.what());
return;
}
// Print the message
Serial.println("New Tag");
card->print();
return;
}
int Card::save_card(string value, string datetime) {
sqlite3 **db = open_db();
// Prepare the statement
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(*db, "INSERT INTO card (id, value, date_created) VALUES (?, ?, ?);", -1, &statement, NULL) !=
SQLITE_OK) {
sqlite3_close_v2(*db);
throw runtime_error("Unable to prepare: " + string(sqlite3_errmsg(*db)));
}
// Bind the tag id, value, and datetime created
const char *values[3] = {this->get_id().c_str(), value.c_str(), datetime.c_str()};
const char *tags[3] = {"id", "value", "datetime"};
for (int i = 0; i < 3; i++) {
if (sqlite3_bind_text(statement, i + 1, values[i], -1, SQLITE_TRANSIENT) != SQLITE_OK) {
sqlite3_finalize(statement);
sqlite3_close_v2(*db);
throw runtime_error("Unable to bind " + string(tags[i]) + ": " + string(sqlite3_errmsg(*db)));
}
}
// Execute the statement
if (sqlite3_step(statement) != SQLITE_DONE) {
sqlite3_finalize(statement);
sqlite3_close_v2(*db);
throw runtime_error("Unable to execute: " + string(sqlite3_errmsg(*db)));
}
sqlite3_finalize(statement);
sqlite3_close_v2(*db);
this->value = value;
this->time = datetime;
return 1;
}
Так что это соответствующий код для проблемы. Когда я печатаю идентификатор карты в функциях loop
или edition_mode
, он печатает правильные вещи. Но когда я печатаю его внутри функции save_card
, он просто печатает B. Теперь я хотел убедиться, что это происходит только внутри этой функции, и это так. В других функциях класса идентификатор тот же, что был сохранен с самого начала.
Еще стоит отметить, что я напечатал адреса указателей классов в трех функциях (save_card
, loop
и edition_mode
), и все они были одинаковыми. Однако, когда я напечатал адрес card->get_id()
, в loop
и в edition_mode
он был одинаковым, а в save_card
что-то совершенно другое. Я пробовал то же самое между edition_mode
и другими функциями класса, и пока адреса не совпадают, они просто меняют предпоследний символ.
Я не эксперт в С++ (очевидно), поэтому я не знаю, что еще делать. Я попытался найти эти адреса, чтобы посмотреть, изменилось ли что-нибудь, но я не думаю, что это изменило. Я также не думаю, что это имеет какое-то отношение к ESP32, а не ко мне и моей глупости. Я надеюсь, что это хорошо объяснено, и я найду кого-то, кто мне поможет. Спасибо всем за ваше время!
Обновлено:
Кто-то спросил, что вернул select_card()
. Вот код:
Card *Card::select_card() {
sqlite3 **db = open_db();
// Prepare the statement
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(*db, "SELECT value, date_created FROM card WHERE id = ?;", -1, &statement, NULL) !=
SQLITE_OK) {
sqlite3_close_v2(*db);
throw runtime_error("Unable to prepare: " + string(sqlite3_errmsg(*db)));
}
// Bind the value
if (sqlite3_bind_text(statement, 1, this->id.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK) {
sqlite3_finalize(statement);
sqlite3_close_v2(*db);
throw runtime_error("Unable to bind: " + string(sqlite3_errmsg(*db)));
}
// Get the columns
string value;
while (sqlite3_step(statement) != SQLITE_DONE) {
value = (char *)sqlite3_column_text(statement, 0);
string datetime = (char *)sqlite3_column_text(statement, 1);
sqlite3_finalize(statement);
sqlite3_close_v2(*db);
this->value = value;
this->time = datetime;
return this;
}
sqlite3_finalize(statement);
sqlite3_close_v2(*db);
return this;
}
Можете ли вы создать отдельный код, который компилируется или дать ссылку на github, где ваш код? Также отметьте комментариями, где ваш код не работает
Я добавил код select_card
, чтобы вы могли все видеть @Miles Budnek В любом случае, объект работает везде, так что я не вижу, в чем проблема.
@nvn Я не могу предоставить вам доступ к GitHub, так как не хочу освобождать код, извините. Однако я думаю, что то, что я только что обновил, поможет вам понять. Пожалуйста, поймите, почему я не могу освободить код. Спасибо
Card(tag).select_card()
кажется гарантированным UB, поскольку он возвращает this
, указатель на Card
, который уничтожается в следующий момент.
Почему он уничтожается? @ALX23z
Потому что это временный объект. Их время жизни заканчивается после выполнения строки.
Поведение вашей программы не определено.
card = Card(tag).select_card();
Здесь вы создаете временный объект Card
, а затем назначаете ему указатель (через select_card
) на card
. Время жизни временного объекта Card
заканчивается в конце полного выражения, а card
не указывает ни на что. Любая дальнейшая попытка разыменования card
приводит к неопределенному поведению.
Я не уверен, почему вы считаете, что здесь вообще нужно использовать указатель. Я не вижу особых причин для этого. Просто создайте локальный объект Card
:
Card card(tag);
try {
card.select_card();
} catch (const runtime_error &e) {
Serial.println(e.what());
return;
}
Я использовал указатель, потому что когда я передаю карту в функцию edition_mode
, если это не указатель, то она не работает, поля вдруг становятся пустыми. Однако я могу создать карту, как вы сказали, а затем передать указатель на нее, возможно, это сработает. Я буду держать вас в курсе. Спасибо! Можете ли вы объяснить, почему то, что я делал, было неправильным?
Итак, я сделал то, что вы сказали, и теперь это работает. Спасибо! Можете ли вы объяснить, почему это временный объект? Я предполагаю, что это потому, что ему нужно освободить память, но я просто хочу подтвердить.
@Joaquin Это временный объект, потому что так работает язык. Самый простой способ представить это так: если объект не имеет имени или создан выражением new
, то он временный. Есть несколько исключений, но это основное правило.
card = Card(tag).select_card()
выглядит очень подозрительно. Что возвращаетselect_card
? Как он распределяется? Вы уверены, что его жизни достаточно?