Код C++ выдает два очень разных вывода при попытке напечатать один и тот же массив строк дважды подряд

Я пытаюсь сделать простую консольную игру pacman, и я испытываю этот неясный вывод на печать из следующего исходного кода:

#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
int main(){
    std::ifstream map_file;
    int map_width, map_height;
    try{
        map_file.open("map.txt");
    }
    catch(int e){
        std::cout << "An exception occured." << std::endl;
    }
    map_file >> map_width;
    map_file >> map_height;
    char* map[map_height];
    for(int i = 0; i < map_height; i++){
        std::string temp_line;
        getline(map_file, temp_line);
        map[i] = (char*)temp_line.c_str();
        std::cout << map[i] << std::endl;

    }
    system("pause");
    for(int i = 0; i < map_height; i++){
        std::cout << map[i] << std::endl;

    }
    return 0;   
}

Я снова скопирую два запуска вызова std::cout из этого кода и приложу скриншот того, что было выведено в консоль:

    for(int i = 0; i < map_height; i++){
        std::string temp_line;
        getline(map_file, temp_line);
        map[i] = (char*)temp_line.c_str();
        std::cout << map[i] << std::endl;

    }

Другой тираж:

    system("pause");
    for(int i = 0; i < map_height; i++){
        std::cout << map[i] << std::endl;

    }

Вот скриншот: блок текста перед system("pause") является содержимым входного файла map.txt и отображается именно так, как написано в map.txt, но второй тираж совершенно неожиданный.

непонятный скриншот распечатки

Мой вопрос заключается в том, что может быть причиной этого.

РЕДАКТИРОВАТЬ: я понял

map[i] = (char*)temp_line.c_str();

выполняет поверхностную, а не глубокую копию, поэтому я решил проблему, вместо этого динамически выделяя

char[map_width + 1]

at

map[i]

и выполнение

strcpy(map[i], temp_line.c_str());

Мне все еще интересно, как могла быть написана исходная программа

ystem32\cmd.exe
ystem32\cmd.exe
ystem32\cmd.exe
ystem32\cmd.exe
ystem32\cmd.exe
ystem32\cmd.exe
ystem32\cmd.exe
ystem32\cmd.exe
ystem32\cmd.exe
ystem32\cmd.exe
ystem32\cmd.exe
ystem32\cmd.exe
ystem32\cmd.exe
ystem32\cmd.exe
ystem32\cmd.exe
ystem32\cmd.exe
ystem32\cmd.exe
ystem32\cmd.exe
ystem32\cmd.exe

person tgudelj    schedule 15.11.2016    source источник
comment
Для вашего редактирования убедитесь, что вы также сохраняете достаточно места для нулевого терминатора (например, temp.length() + 1 пробел). Если это не сработает, предоставьте свой самый последний код.   -  person SenselessCoder    schedule 16.11.2016
comment
Для справки в будущем лучше использовать предложение Пэдди, поскольку вопрос помечен как C++, и использование этих методов поможет вам легче справиться с этими проблемами.   -  person SenselessCoder    schedule 16.11.2016
comment
Как только ваш код переходит в неопределенное поведение, только Кром знает, какими будут результаты. Возможно, вы получите лишнюю хрень в командной строке. Возможно, ваш компьютер обретет разум, захватит контроль над мировыми ядерными арсеналами и уничтожит человечество. Я бы предпочел первое второму, большое спасибо.   -  person user4581301    schedule 16.11.2016


Ответы (4)


Неопределенное поведение. Вы храните указатели, которые больше не действительны:

map[i] = (char*)temp_line.c_str();

Если бы ваш map хранил значения std::string вместо указателей, было бы неплохо сделать это:

map[i] = temp_line;

Я также заметил, что вы используете массивы переменной длины. Не. Вместо этого используйте std::vector. Проще всего для новичка сделать это так:

std::vector<std::string> map( map_height );
for( int i = 0; i < map_height; i++ )
{
    getline( map_file, map[i] );
}
person paddy    schedule 15.11.2016

Это потому, что ваша строка temp выходит за пределы области видимости, а вместе с ней и связанный с ней указатель (c_str). Итак, ваш map[i] указывает на мусорные данные. Вам нужно глубоко скопировать содержимое с помощью чего-то вроде strcpy. См. strcpy, чтобы узнать, как использовать для этого strcpy. (Подсказка: вам нужно фактически выделить память для исходной строки, а также нулевой терминатор)

Это также UB (неопределенное поведение). (Пытаюсь печатать указатели после проигрыша, т.е.)

person SenselessCoder    schedule 15.11.2016
comment
strcpy не глубоко копирует содержимое. - person paddy; 16.11.2016
comment
@paddy, если вы говорите само по себе, ну, это идет с контрактом, согласно которому вы предоставляете достаточно места в строке назначения. Так что, я думаю, в этом смысле это не так, но я все же скажу, что это позволяет вам глубоко копировать. - person SenselessCoder; 16.11.2016
comment
Если вы используете термин «глубокое копирование» по отношению к строкам, вы подразумеваете, что необходимые распределения памяти также выполняются. Вы рекомендовали заменить UB в форме незаконных чтений на UB, который вместо этого выполняет незаконные записи. - person paddy; 16.11.2016
comment
Не обязательно незаконно пишет. Я предполагал, что ОП проверит, что делает strcpy и как он используется, если они не знают. В любом случае, я отредактирую свой пост. - person SenselessCoder; 16.11.2016

for(int i = 0; i < map_height; i++){
    std::string temp_line;
    getline(map_file, temp_line);
    map[i] = (char*)temp_line.c_str();
    std::cout << map[i] << std::endl;

}

Вы сохраняете внутреннюю строку c переменной temp_line, но переменная temp_line уничтожается после каждой итерации вышеуказанного цикла. Таким образом, ваш массив переменных char* указывает на случайный мусор.

std::string map[map_height];
for(int i = 0; i < map_height; i++){
    getline(map_file, map[i]);
    std::cout << map[i] << std::endl;

}
system("pause");
for(int i = 0; i < map_height; i++){
    std::cout << map[i] << std::endl;

}
person pat    schedule 15.11.2016

Как вы, вероятно, подозреваете, «случайный мусор», который вы видите, на самом деле не случайный. Да, это следствие неопределенного поведения, и вы не должны его рассматривать, но почему именно эта конкретная последовательность символов?

Я подозреваю, что это содержимое argv[0]. Когда ОС вызывает приложение, она вызывает main и передает два параметра: argc (количество аргументов) и argv (массив, содержащий аргументы командной строки). argv[0] — это имя вашего приложения. C:\Windows\System32\cmd.exe — это командная строка и часто родительский процесс приложения, поэтому я мог представить, что ОС записала эту строку в первые несколько фрагментов памяти приложения.

Поскольку в вашем коде используется определение int main() вместо определения int main(int argc, char* argv[]), ваш код не предназначен для доступа к этому блоку памяти, но (не так) произвольный доступ указывает одной из ваших строк на то место в памяти, которое находится в стеке и раннее в регистре памяти программы.

person Stewart    schedule 05.02.2018