运行程序时出现C++堆已损坏错误

3
我有一个程序,每次运行它时,有时(大多数情况下)会出现堆破坏错误。我无法确定它发生的位置,因为每次运行它都会在程序中的不同位置弹出。请问有人能提供一些帮助吗?顺便说一句,当我尝试释放p时也会出现堆破坏的情况。谢谢!
#define _CRT_SECURE_NO_WARNINGS
#include "stdafx.h"
#include<iostream>
#include<string.h>

using namespace std;
const int MAX_OF_PLAYERS = 10;
const int SIZE = 100;

struct player_t {
    char *name;
    int numOfShirt;
};
struct team_t {
    char *nameOfTeam;
    int maxOfPlayers;
    int numOfPlayers;
    player_t *players;

};

void readPlayer(player_t *player);
void initTeam(team_t *team);
void addPlayer(team_t *team);
void printTeam(team_t *team);
void freeAll(team_t *team);
player_t** getAllPlayersStartWithA(team_t *team);
void printAteam(player_t **p);

int main()
{
    team_t t;
    player_t **p;
    initTeam(&t);
    addPlayer(&t);
    addPlayer(&t);
    printTeam(&t);
    p = getAllPlayersStartWithA(&t);
    if (p[0] != NULL)
        printAteam(p);
    system("pause");
    freeAll(&t);
    //delete[] p;
}

void readPlayer(player_t *player)
{
    char name[SIZE];
    cout << " please enter the name of the player " << endl;
    cin >> name;
    cout << " please enter the num of the shirt " << endl;
    cin >> player->numOfShirt;
    int size = strlen(name);
    char *res = new char[size + 2];
    strcpy(res, name);
    player->name = res;
}
void initTeam(team_t *team)
{

    char name[SIZE];

    // get the team name
    cout << " please enter your team name" << endl;
    cin >> name;
    // get the name length
    int size = strlen(name);
    // allocate new array with length size
    team->nameOfTeam = new char[size + 1];
    // copy the string to the new array
    strcpy(team->nameOfTeam, name);

    // get the number of max players
    cout << "please enter the number of the max players on your team" << endl;
    cin >> team->maxOfPlayers;

    // create new players array
    player_t *players = new player_t[team->maxOfPlayers];
    // initial the players array
    for (int i = 0; i < team->maxOfPlayers; i++)
    {
        players[i] = { 0 };
    }
    //bind the array to team
    team->players = players;

    // set current players to 0
    team->numOfPlayers = 0;

}
void addPlayer(team_t *team)
{

    for (int i = 0; i < team->maxOfPlayers; i++)
    {
        if (team->players[i].name == NULL)
        {
            readPlayer(team->players + i);
            break;
        }
    }

}
void printTeam(team_t *team)
{

    cout << "Team name: ";
    cout << team->nameOfTeam << endl;

    cout << "Max Number of players in team: ";
    cout << team->maxOfPlayers << endl;

    cout << "Current number of players in team: ";
    cout << team->numOfPlayers << endl;

    cout << "Team Players:" << endl;
    for (int i = 0; i < team->maxOfPlayers; i++)
    {
        if (team->players[i].name)
        {
            cout << "Player name: ";
            cout << team->players[i].name;
            cout << ", ";
            cout << "Player shirt: ";
            cout << team->players[i].numOfShirt << endl;
        }
    }
    cout << endl;
}
void freeAll(team_t *team)
{
    for (int i = 0; i < team->maxOfPlayers; i++)
    {
        if ((team->players + i)->name != NULL)
            delete[](team->players + i)->name;
    }

    delete[] team->players;
}
player_t** getAllPlayersStartWithA(team_t *team)
{
    int sum = 0, position = 0;
    for (int i = 0; team->players[i].name != NULL; i++)
    {
        if (team->players[i].name[0] == 'a' || team->players[i].name[0] == 'A')
        {
            sum++;
        }
    }

    player_t **p = new player_t*[sum + 1];

    for (int i = 0; i < team->maxOfPlayers; i++)
    {
        p[i] = NULL;
    }

    for (int i = 0; team->players[i].name != NULL; i++)
    {
        if (team->players[i].name[0] == 'a')
        {
            p[position++] = team->players + i;
        }
    }
    return p;
}
void printAteam(player_t **p)
{
    cout << "Players start with 'A': " << endl;
    for (int i = 0; p[i] != NULL; i++)
    {
        cout << "Player name: ";
        cout << (p[i]->name);
        cout << ", ";
        cout << "Player shirt: ";
        cout << (p[i]->numOfShirt) << endl;
    }
}

1
尽管堆损坏错误很难诊断(欢迎来到C++!),但您是否尝试使用调试器逐步执行代码并检查变量的值?编辑:为什么在结构体内部使用char*而不是std::string?为什么将对象的指针传递给函数而不是引用? - Algirdas Preidžius
1
你正在使用C++编程,所以最好使用类和STL容器来使编程更轻松。在大多数情况下,使用裸指针都是不明智的,会给你带来很多麻烦。只有在有正当理由时才使用它们。 - Soeren
一个潜在的问题在这里 for (int i = 0; team->players[i].name != NULL; i++)。如果球队有一整套球员,那么他们中没有人会有空名字。 - Bo Persson
@AlgirdasPreidžius 是的,我一步一步地进行了调试,每次堆破坏错误都在不同的位置弹出,有时程序没有任何错误。 我使用char*是因为这是学校作业,并且他们定义了这些结构体,所以我无法更改。 - Brec
@Soeren 正如我所提到的,这是一项学校作业,我不能使用类。我有严格的规定。 - Brec
显示剩余2条评论
2个回答

14

我没有审查整个代码,但有可用的工具可以帮助您在此情况下跟踪内存使用情况,并指示是否出现问题。一个例子是 valgrind,它至少适用于 Linux 环境。无论如何,这个工具让我至少发现了您代码中的一个错误,如下所示。

  1. 使用调试信息进行编译。如果您正在使用 gcc,请使用 -g 命令行标志,例如:

    g++ foo.cpp -g -o foo -std=gnu++11
    
  2. 使用valgrind运行

    valgrind ./foo
    
  3. 查看输出结果

    ==6423== Memcheck, a memory error detector
    ==6423== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
    ==6423== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
    ==6423== Command: ./foo
    ==6423== 
     please enter your team name
    sdfads
    please enter the number of the max players on your team
    3
     please enter the name of the player 
    efwf
     please enter the num of the shirt 
    5
     please enter the name of the player 
    dsfdsa
     please enter the num of the shirt 
    3
    Team name: sdfads
    Max Number of players in team: 3
    Current number of players in team: 0
    Team Players:
    Player name: efwf, Player shirt: 5
    Player name: dsfdsa, Player shirt: 3
    
    ==6423== Invalid write of size 8
    ==6423==    at 0x4011FF: getAllPlayersStartWithA(team_t*) (foo.cpp:155)
    ==6423==    by 0x400C08: main (foo.cpp:38)
    ==6423==  Address 0x5ab6668 is 0 bytes after a block of size 8 alloc'd
    ==6423==    at 0x4C2E80F: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==6423==    by 0x4011CC: getAllPlayersStartWithA(team_t*) (foo.cpp:151)
    ==6423==    by 0x400C08: main (foo.cpp:38)
    ==6423== 
    ==6423== 
    ==6423== HEAP SUMMARY:
    ==6423==     in use at exit: 72,719 bytes in 3 blocks
    ==6423==   total heap usage: 8 allocs, 5 frees, 74,829 bytes allocated
    ==6423== 
    ==6423== LEAK SUMMARY:
    ==6423==    definitely lost: 15 bytes in 2 blocks
    ==6423==    indirectly lost: 0 bytes in 0 blocks
    ==6423==      possibly lost: 0 bytes in 0 blocks
    ==6423==    still reachable: 72,704 bytes in 1 blocks
    ==6423==         suppressed: 0 bytes in 0 blocks
    ==6423== Rerun with --leak-check=full to see details of leaked memory
    ==6423== 
    ==6423== For counts of detected and suppressed errors, rerun with: -v
    ==6423== ERROR SUMMARY: 2 errors from 1 contexts (suppressed: 0 from 0)
    
  4. 根据此输出,显然您在第155行有问题。

  5. ==6423== Invalid write of size 8
    ==6423==    at 0x4011FF: getAllPlayersStartWithA(team_t*) (foo.cpp:155)
    

    而如果我们更仔细地观察,会发现以下内容:

    player_t **p = new player_t*[sum + 1];
    for (int i = 0; i < team->maxOfPlayers; i++)
    {
         p[i] = NULL;
    }
    

    你创建了一个大小为sum + 1的数组,但是迭代它却高达team->maxOfPlayers可能相同也可能不同。这意味着你写入了一些超出想要修改的数组之外的内存,因此你在堆中写入了一些本不应该写入的地方(导致了堆破坏)。

    这至少是一个问题。重复步骤1-4,直到valgrind没有其他要抱怨的地方。


2
只有一个评论:并不是所有人都使用g++编译他们的C++代码,也不是所有人都使用与valgrind兼容的平台。由于OP没有描述他们正在使用的平台,答案应该包含平台无关的问题解决建议,或者不包含这个基于偏好的建议。 - Algirdas Preidžius
1
我必须承认,我不知道 Visual Studio 中是否有适用的工具,但我建议有人编辑答案(或发布另一个答案),描述其使用方法。使用这些工具会大有裨益,为什么要避免发布此类答案呢?然而,我将编辑答案并指明平台。 - koalo
解决了我的问题,你能否指导我一下如何调试类似这样的问题? - Brec
你可以在这里找到一个:http://www.cprogramming.com/debugging/valgrind.html,另一个在这里:http://cs.ecs.baylor.edu/~donahoo/tools/valgrind/。开始并不容易,特别是如果你从未使用过命令行工具,并且很大程度上取决于你的编程环境,正如之前所指出的那样,但它值得付出努力,长远来看真的可以节省时间。 - koalo
我使用的是较旧的Mac OSX(10.9),不幸的是无法运行稳定版本的Valgrind。我想知道是否有其他解决此问题的方法。 - synchronizer
在gcc中也有一个名为AddressSanitizer的模块(使用-fsanitize=address启用),clang也有,也许它们适用于Mac OS X 10.9。 - koalo

1
你需要更加小心地访问数组并跟踪它们的大小。
正如评论中已经指出的那样,for (int i = 0; team->players[i].name != NULL; i++)会带来麻烦。你应该确保循环受到限制,可以是players数组中的总条目数(可能是maxOfPlayers),也可以是当前有效球员的数量,似乎应该是numOfPlayers。但是,当添加新条目时,你的代码从未实际递增numOfPlayersgetAllPlayersStartWithA中的以下代码也是一个问题:
player_t **p = new player_t*[sum + 1];

for (int i = 0; i < team->maxOfPlayers; i++)
{
    p[i] = NULL;
}

sum + 1 可能远小于 maxOfPlayers。在这种情况下,for循环将会覆盖数组末尾之外的内存。这很可能是导致当前堆栈错误的原因。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接