如何调试运行时检查失败#2?

How to debug Run-Time Check Failure #2?

提问人:PRACTICAL PIG 提问时间:10/23/2023 最后编辑:JabberwockyPRACTICAL PIG 更新时间:10/23/2023 访问量:73

问:

运行以下代码以计算随机七张扑克牌中没有对、一对、两对、三对、满屋和四张的概率时,Visual Studio 在最后一行返回警告“运行时检查失败 #2 - 围绕变量”hand_counts“的堆栈已损坏”。但是,程序仍然可以通过单击“继续”来输出结果。我已经检查了程序是否存在越界数组访问,但没有找到任何访问。可能导致错误的原因是什么?

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
typedef enum suit
{
    spades,
    hearts,
    diamonds,
    clubs
} suit;
typedef struct card
{
    suit suit;
    short pip;
} card;
void deck_shuffle(card deck[52])
{
    srand(time(NULL));
    for (int i = 0; i < 52; i++)
    {
        const int j = rand() % 52;
        const card temp = deck[i];
        deck[i] = deck[j];
        deck[j] = temp;
    }
}
int possibility(card hand[7])
{
    int pair_type = 0;
    int count = 0;
    for (int i = 0; i < 6; i++)
    {
        for (int j = i + 1; j < 7; j++)
        {
            if (hand[i].pip == hand[j].pip)
            {
                count++;
            }
        }
    }
    // check for no pairs.
    if (count == 0)
    {
        pair_type = 1;
    }
    // check for one pair.
    if (count == 1)
    {
        pair_type = 2;
    }
    // check for two pair.
    if (count == 2)
    {
        pair_type = 3;
    }
    // check for three of a kind.
    if (count == 3)
    {
        pair_type = 4;
    }
    // check for full house.
    int rank1 = 0;
    int rank2 = 0;
    int temp1 = hand[0].pip;
    int temp2;
    int count1 = 0;
    int count2 = 0;
    for (int i = 0; i < 6; i++)
    {
        for (int j = i + 1; j < 7; j++)
        {
            if (hand[i].pip == hand[j].pip)
            {
                rank1 = hand[j].pip;
                if (temp1 == rank1)
                {
                    count1++;
                    temp1 = rank1;
                }
                else
                {
                    temp2 = rank2;
                    rank2 = hand[j].pip;
                    if (temp2 == rank2)
                    {
                        count2++;
                    }
                }
            }
        }
    }
    count2 += 1;
    if ((count1 == 3 && count2 == 1) || (count1 == 3 && count2 == 3))
    {
        pair_type = 5;
    }
    // check for four of a kind.
    if (count1 == 6 || count2 == 6)
    {
        pair_type = 6;
    }
    return pair_type;
}
int main(void)
{
    card deck[52];
    for (int i = 0; i < 52; i++)
    {
        deck[i].suit = i / 13;
        deck[i].pip = i % 13;
    }
    int hand_counts[6] = { 0 };
    for (int i = 0; i < 1000000; i++)
    {
        deck_shuffle(deck);
        card hand[7];
        for (int j = 0; j < 7; j++)
        {
            hand[j] = deck[j];
        }
        int hand_type = possibility(hand);
        hand_counts[hand_type - 1]++;
    }
    for (int i = 0; i < 6; i++)
    {
        printf("The probability of hand type %d: is %f\n", i + 1, (double)hand_counts[i] / 1000000);
    }
    return 0;
}

我尝试在其他 IDE 中运行相同的代码,但没有警告。结果的计算速度也比在 Visual Studio 中更快。我不确定我的源代码中是否存在错误,或者是否是 Visual Studio 本身的问题。

c 调试 结构 运行时错误 visual-studio-debugging

评论

0赞 Jabberwocky 10/23/2023
检查是否没有数组索引越界。hand_counts
0赞 Jabberwocky 10/23/2023
提示:如果您的代码在一个开发平台上运行良好,但在另一个开发平台上运行不正常,这是一个非常强烈的迹象,表明问题出在您的代码中,而不是在平台内。谷歌“C undefined habaviour”。超出范围的索引会导致未定义的行为。
1赞 Ken Y-N 10/23/2023
possibility()返回等于 0 到 6,但随后您这样做了,因此在 0 时,您将覆盖来自其他地方的内存,这是未定义的行为。另外,在这里搜索或谷歌搜索更简单但更彻底的洗牌算法。hand_typehand_counts[hand_type - 1]++;
0赞 PRACTICAL PIG 10/23/2023
@Ken Y-N 感谢您的帮助!我仍在尝试找出不符合检查一对或其他条件的 7 张扑克牌的具体组合,但我认为这就是错误的原因。

答:

3赞 Fe2O3 10/23/2023 #1

对不起,但末尾的多个循环和变量有点难以理解。我稍微剥离了一些内容,并放入了一个特定的测试用例;4张王牌:possibility()

int possibility( int hand[7]) {
    int pair_type = 0;

    /* ... */

    for (int k = 0; k < 7; k++) printf( "%d ", hand[k] );

    int rank1 = 0;
    int rank2 = 0;
    int temp1 = hand[0];
    int temp2;
    int count1 = 0;
    int count2 = 0;
    for (int i = 0; i < 6; i++) {
        for (int j = i + 1; j < 7; j++) {
            if (hand[i] == hand[j]) {
                rank1 = hand[j];
                if (temp1 == rank1) {
                    count1++;
                    temp1 = rank1;
                } else {
                    temp2 = rank2;
                    rank2 = hand[j];
                    if (temp2 == rank2)
                        count2++;
                }
            }
        }
    }

    count2 += 1;
    printf( "\nSuspicion: count1 = %d count2 = %d\n", count1, count2 );
    /* ... */
    return pair_type;
}

int main(void) {
    int hand[7] = { 12, 0, 0, 0, 0, 1, 2 };

    printf("hand type %d\n", possibility(hand) );

    return 0;
}

结果:

12 0 0 0 0 1 2
Suspicion: count1 = 0 count2 = 7   // <<<<
hand type 0

然后:

        int hand_type = possibility(hand);
        hand_counts[hand_type - 1]++;

将增加位置...绝对是禁忌。hand_counts[ 0 - 1 ]

你需要回到那个嵌套循环,弄清楚如何避免重复计算。4-of-a-a-kind 可以发生在任何位置。循环中发生了太多的重复计数for()

建议你研究一下卡片的“直方图”计数(这将是一个开始。然后,在手上传递一次,就会得到你需要的计数。int cnt[13] = {0};

"或者如果这是 Visual Studio 本身的问题。虽然这总是有可能的,但一个可怜的工匠一开始就责怪他/她的工具。


你知道的...如果你没有尝试使用一个精确为 6 和只有 6 个“类型”的数组,然后继续减去 1 以使用从 0 开始的索引,你自己可能已经发现了这一点......

int hand_counts[ 27 ] = { 0 }; // why not?
/* ... */
    for (int i = 0; i < sizeof hand_counts/sizeof hand_counts[0]; i++)
    {
        printf("The probability of hand type %d: is %f\n", i, (double)hand_counts[i] / 1000000);
    }

(刚刚注意到您在打印报表中“添加 1”!太多的“聪明”溢出而变得“愚蠢”......


加班:很感兴趣,这里有一个精简版本,可能适合详细说明成更大的内容:

void deck_shuffle( int deck[] ) {
    for( int i = 0; i < 52; i++ ) {
        const int j = rand() % 52;
        const int temp = deck[i];
        deck[i] = deck[j];
        deck[j] = temp;
    }
}

int cmp( const void *a, const void *b ) {
    return *(int*)b - *(int*)a; // descending sqnc
}

enum { nada, four, three, two, twoPair, fullhouse, nTallies };

int possibility( int hand[] ) {
    int cnt[13] = { 0 };

    for( int i = 0; i < 7; i++ ) {
        printf( "%c ", "A234567890JQK"[ hand[i] ] );
        ++cnt[ hand[i] ];
    }

//  qsort( cnt, sizeof cnt[0], 13, cmp ); // Wrong!
    qsort( cnt, 13, sizeof cnt[0], cmp ); // descending

    if( cnt[0] == 4 ) return four;

    if( cnt[0] == 3 )
        if( cnt[1] == 2 )
            return fullhouse;
        else
            return three;

    if( cnt[0] == 2 )
        if( cnt[1] == 2 )
            return twoPair;
        else
            return two;

    return nada;
}

int main( void ) {
    srand(time(NULL));

    int deck[52];

    for (int i = 0; i < 52; i++)
        deck[i] = i % 13;

    int hand_counts[ nTallies ] = { 0 };

    for( int r = 0; r < 100; r++ ) {
        deck_shuffle( deck );

        int p = possibility( deck ); // look at top 7 cards
        printf( " ... %2d\n", p );

        ++hand_counts[ p ];
    }

    for( int out = 0; out < nTallies; out++ )
        printf("The probability of hand type %d: is %f\n", out, (double)hand_counts[out] / 100);

    return 0;
}

评论

1赞 PRACTICAL PIG 10/23/2023
感谢您的详细分析。我真的很感激。我现在意识到我没有足够仔细地考虑我的代码,这就是导致问题的原因。看来我以后在提问之前需要仔细分析代码。
0赞 Fe2O3 10/23/2023
@PRACTICALPIG 它填补了时间。我所做的第一次分析是仓促的,不适用。返回到您的代码并重试。刚刚添加了一个您可能会觉得有趣的“精简版”。卡片“花色”会分散注意力。在添加华丽之前,首先测试逻辑中的 h*ll。这都是一个学习曲线......:-)
0赞 Fe2O3 10/23/2023
@PRACTICALPIG 发现了我通常的失误。参数顺序错误。希望这不会给你带来任何时间损失......qsort()
0赞 PRACTICAL PIG 10/24/2023
我非常感激。我认为你的方法更容易理解,可以更好地解决问题。我会试一试。
0赞 Fe2O3 10/24/2023
@PRACTICALPIG 被遗忘的扑克比我所知道的还要多......刚刚问谷歌“赢扑克牌”就知道有 10 个类别,有些不是“花色不可知”(例如:“同花顺”、皇家或其他。最清楚的做法是使用循环,从最高值开始检查牌可能形成的最佳“手牌”。没有人会想将 4 张 A 作为“2 对”,对吧? (可能是这方面的一个非常好的朋友!:-)qsort()