Функция класса С++ не использует правильные значения в `this`, но делает это при использовании «внешнего» объекта

У меня есть проект ESP32, в котором есть класс с именем Card, который представляет тег NFC. В этом классе 3 поля, id, значение и time_saved. Класс также имеет функцию сохранения тега в базе данных, хранящейся на SD. Когда модуль читает карту, он возвращает ее uid. Затем он создает указатель на Card. Затем он ищет uid в базе данных. Если ее нет, программа спрашивает, хочет ли пользователь сохранить карту. Когда они говорят «да» и вводят сообщение, программа использует функцию Cardsave_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;
}
card = Card(tag).select_card() выглядит очень подозрительно. Что возвращает select_card? Как он распределяется? Вы уверены, что его жизни достаточно?
Miles Budnek 16.04.2023 06:56

Можете ли вы создать отдельный код, который компилируется или дать ссылку на github, где ваш код? Также отметьте комментариями, где ваш код не работает

nvn 16.04.2023 07:06

Я добавил код select_card, чтобы вы могли все видеть @Miles Budnek В любом случае, объект работает везде, так что я не вижу, в чем проблема.

Joaquin 16.04.2023 07:14

@nvn Я не могу предоставить вам доступ к GitHub, так как не хочу освобождать код, извините. Однако я думаю, что то, что я только что обновил, поможет вам понять. Пожалуйста, поймите, почему я не могу освободить код. Спасибо

Joaquin 16.04.2023 07:17
Card(tag).select_card() кажется гарантированным UB, поскольку он возвращает this, указатель на Card, который уничтожается в следующий момент.
ALX23z 16.04.2023 07:38

Почему он уничтожается? @ALX23z

Joaquin 17.04.2023 01:33

Потому что это временный объект. Их время жизни заканчивается после выполнения строки.

ALX23z 17.04.2023 02:16
Стоит ли изучать 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
7
62
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Поведение вашей программы не определено.

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 16.04.2023 18:57

Итак, я сделал то, что вы сказали, и теперь это работает. Спасибо! Можете ли вы объяснить, почему это временный объект? Я предполагаю, что это потому, что ему нужно освободить память, но я просто хочу подтвердить.

Joaquin 17.04.2023 02:23

@Joaquin Это временный объект, потому что так работает язык. Самый простой способ представить это так: если объект не имеет имени или создан выражением new, то он временный. Есть несколько исключений, но это основное правило.

Miles Budnek 17.04.2023 04:10

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