在 SPOJ 上提交代码会导致运行时错误 (SIGABRT)

Posted

技术标签:

【中文标题】在 SPOJ 上提交代码会导致运行时错误 (SIGABRT)【英文标题】:Code submission on SPOJ gives runtime error (SIGABRT) 【发布时间】:2020-07-23 03:44:15 【问题描述】:

我在 SPOJ 上做了一个exercise 来练习高级算法。


问题陈述如下:

Harish 去超市为他的“n”个朋友买了“k”公斤的苹果。超市真的很奇怪。物品的定价非常不同。他去苹果区询问价格。售货员给了他一张卡片,他发现苹果的价格不是每公斤。苹果被装在盖子里,每个盖子里有‘x’公斤的苹果,x > 0,‘x’是一个整数。 “x”公斤包的价值为“y”卢比。因此,标语牌包含一个表格,其中有一个条目“y”,表示“x”公斤包的价格。如果‘y’为-1,则表示对应的数据包不可用。现在由于苹果只能以小包的形式购买,他决定最多为他的“n”个朋友购买“n”包,即他不会购买超过 n 包的苹果。 Harish 非常喜欢他的朋友,所以他不想让他的朋友失望。所以现在,他会告诉你他有多少朋友,你必须告诉他他必须为朋友花的最低金额。


这是我用来解决问题的代码:

#include <algorithm>
#include <iostream>
#include <vector>

using std::cout;
using std::cin;
using std::vector;
using std::endl;

int MinValueOf(int a, int b)

    return (a < b) ? a : b;

int BuyingApple(vector<int> PriceaTag, int Friends, int KilogramsToBuy)

    vector<vector<int>> Table(Friends + 1, vector<int>(KilogramsToBuy + 1, 0));
    for(int i = 1; i <= Friends; i++)
    
        for(int j = 0; j <= i; j++)
        
            Table[i][j] = INT32_MAX;
            if(j == 0)
                Table[i][0] = 0;
            else if(PriceaTag[j] > 0)
                Table[i][j] = MinValueOf(Table[i][j], Table[i - 1][i - j] +  PriceaTag[j]);
        
    
    return (Table[Friends][KilogramsToBuy] == 0) ? -1 : Table[Friends][KilogramsToBuy];

int main()

    vector<int> Price;
    int Friends, Kilogram, t;
    cin >> t;
    for(int i = 0; i < t; i++)
    
        cin >> Friends >> Kilogram;
        vector<int> Price(Kilogram + 1, 0);
        for(int i = 1; i <= Kilogram; i++)
        
            cin >> Price[i];
        
        cout << BuyingApple(Price, Friends, Price.size() - 1) << endl;
    
    return 0;


代码的I/O如下:

输入的第一行将包含测试用例的数量,C。 每个测试用例将包含两行。 第一行包含 N 和 K,他的朋友数量以及他应该购买的苹果数量(公斤)。 第二行包含 K 个空格分隔的整数,其中第 i 个整数指定“i”公斤苹果包的价格。值为-1表示对应的数据包不可用。

每个测试用例的输出应该是一行,其中包含他必须为他的朋友花费的最低金额。如果他无法满足他的朋友,则打印 -1。


约束:

0 0 0


但是当我提交我的代码时,我收到了一条消息SIGABRT runtime error,尽管我的代码在Windows compiler (G++ 14)Linux Compiler (G++ Clang 9) 中运行顺利。我试图调试,但我失败了。可能有什么问题?

【问题讨论】:

@d4rk4ng31 即使我将此参数更改为Kilogram,它也会出现此运行时错误。 @d4rk4ng31 是的,我指的是 0-1 背包问题。 Table 额外空间的原因是它易于理解。因为“x > 0 and 'x'是一个整数。一个'x'公斤包的价值是'y'卢比”,这里的朋友数量也大于0。你可能很难理解所以你可以用你自己的方式来帮我解决这个问题 导致失败的测试数据是什么?第二,有std::min,为什么还要写min函数?第三,您很有可能超出了向量的范围。使用at() 而不是[ ] 访问元素将检查您是否越界。最后:虽然我的代码在 Windows 编译器 (G++ 14) 和 Linux 编译器 (G++ Clang 9) 中都能顺利运行——这没什么。如果您越界,那是未定义的行为。如果问题在于越界,如果您使用at(),代码将在每个平台上都失败。 出于好奇,G++ 14?我在CompilerExplorer 上看到的最新的 g++(在trunk 之前)目前是 g++ 10.1,不是吗? 如我所写:一个是编译器g++的(软件)版本,另一个是编译器必须遵守的C++语言标准的版本。 【参考方案1】:

由于这是一个 SPOJ 问题,并且您没有获得测试数据,因此您应该做的是随机化测试,直到失败为止。这样,您可能会得到一个失败的示例案例。这称为fuzzing,是一种可以在您的问题中使用的技术。

以下内容适用于导致分段错误的情况,在某些情况下,用于验证给定输出是否与预期输出匹配。换句话说,与其试图找出测试数据,不如让计算机为你生成测试。

您这样做的方法是查看问题给您的约束,并生成符合约束的随机数据。由于它们都是整数,因此您可以使用 &lt;random&gt; 标头和 uniform_int_distribution 来做到这一点。

以下是使用NK 和价格数据的以下约束对数据进行模糊测试的示例:

Constraints:

0 < N <= 100
0 < K <= 100
0 < price <= 1000

好的,鉴于此信息,我们可以获取您的确切代码,删除 cin 语句,并将所有内容替换为符合约束的随机数据。此外,如果我们使用at() 访问导致问题的函数中的向量,我们可以测试是否存在越界访问。

鉴于所有这些信息,我们可以从更改 main 开始以生成符合问题约束的随机数据:

#include <random>
#include <algorithm>
//...
int main()

    // random number generator
    std::random_device rd;
    std::mt19937 gen(rd());

    // Prices will be distributed from -1 to 1000
    std::uniform_int_distribution<> distrib(-1, 1000);

    // N and K are distributed between 1 and 100  
    std::uniform_int_distribution<> distribNK(1, 100);

    // This one will be used if we need to replace 0 in the Price vector with 
    // a good value 
    std::uniform_int_distribution<> distribPos(1, 1000);

    // our variables
    int Friends;
    int Kilogram;
    vector<int> Price;

    // we will keep going until we see an out-of-range failure
    while (true)
    
        try
        
            // generate random N and K values
            Friends = distribNK(gen);
            Kilogram = distribNK(gen);

            // Set up the Price vector
            Price = std::vector<int>(Kilogram + 1, 0);

            // Generate all the prices
            std::generate(Price.begin() + 1, Price.end(), [&]()  return distrib(gen); );

            // Make sure we get rid of any 0 prices and replace them with a random value
            std::transform(Price.begin() + 1, Price.end(), Price.begin() + 1, [&](int n)
                 if (n == 0) return distribPos(gen);  return n; );

            // Now test the function
            std::cout << BuyingApple(Price, Friends, Price.size() - 1) << std::endl;
        

        catch (std::out_of_range& rError)
        
            std::cout << rError.what() << "\n";
            std::cout << "The following tests cause an issue:\n\n";
            // The following tests cause an issue with an out-of-range.  See the data
            std::cout << "Friends = " << Friends << "\nK = " << Kilogram << "\nPrice data:\n";
            int i = 0;
            for (auto p : Price)
            
                std::cout << "[" << i << "]: " << p << "\n";
                ++i;
            
            return 0;
        
    

鉴于所有这些,我们可以通过将[ ] 替换为at() 来更改BuyingApple 函数:

int BuyingApple(vector<int> PriceaTag, int Friends, int KilogramsToBuy)

    vector<vector<int>> Table(Friends + 1, vector<int>(KilogramsToBuy + 1, 0));
    for (int i = 1; i <= Friends; i++)
    
        for (int j = 0; j <= i; j++)
        
            Table.at(i).at(j) = INT32_MAX;
            if (j == 0)
                Table[i][0] = 0;
            else if (PriceaTag[j] > 0)
                Table[i][j] = MinValueOf(Table[i][j], Table.at(i - 1).at(i - j) + PriceaTag.at(j));
        
    
    return (Table[Friends][KilogramsToBuy] == 0) ? -1 : Table[Friends][KilogramsToBuy];

现在我们有了一个自动案例生成器,它将捕获并显示任何可能导致向量出现问题的案例。请注意,我们会一直循环下去,直到我们得到一个“崩溃”的测试用例。然后我们输出崩溃的案例,现在可以使用这些值来调试问题。

我们使用std::generatestd::transform 来说明如何填充向量(或您的测试使用的任何序列容器),以及如何专门化测试(例如确保Price 没有0值)。另一个 SPOJ 问题可能需要其他专业知识,但希望您了解基本概念。

这是Live Example。

我们看到一个测试用例导致了一个out-of-range 异常被抛出。 main 函数有一个try/catch 来处理这个错误,我们可以看到导致问题的数据。


因此,如果我们的朋友比苹果多,问题就会出现在我们越界的地方。我不会尝试解决此问题,但您现在有一个输入失败的测试用例。

一般来说,如果网站没有向您显示失败的测试用例,您可以在许多(如果不是大多数)“在线法官”网站上使用此技术。

编辑: 更新了 std::transform 中的 lambda,以仅替换 Price 向量中的 0


编辑:这是一个随机字符串模糊器,可以生成模糊字符串数据。

您可以控制字符串的数量、每个字符串的最小和最大大小以及生成每个字符串时将使用的字符的字母表。

#include <random>
#include <string>
#include <vector>
#include <iostream>

struct StringFuzzer
 
    unsigned int maxStrings;  // number of strings to produce
    unsigned int minSize;     // minimum size of a string
    unsigned int maxSize;     // maximum size of the string
    bool fixedSize;           // Use fixed size of strings or random
    std::string alphabet;     // string alphabet/dictionary to use
    
public:
    StringFuzzer() : maxStrings(10), minSize(0), maxSize(10), fixedSize(true), alphabet("abcdefghijklmnopqrstuvwxyz")
    
    StringFuzzer& setMaxStrings(unsigned int val)  maxStrings = val; return *this; ;
    StringFuzzer& setMinSize(unsigned int val)  minSize = val; return *this; ;
    StringFuzzer& setMaxSize(unsigned int val)  maxSize = val; return *this; ;
    StringFuzzer& setAlphabet(const std::string& val)  alphabet = val; return *this; ;
    StringFuzzer& setFixedSize(bool fixedsize)  fixedSize = fixedsize; return *this; 

    std::vector<std::string> getFuzzData() const
    
        // random number generator
        std::random_device rd;
        std::mt19937 gen(rd());

        // Number of strings produced will be between 1 and maxStrings
        std::uniform_int_distribution<> distribStrings(1, maxStrings);

        // string size will be distributed between min and max sizes
        std::uniform_int_distribution<> distribMinMax(minSize, maxSize);

        // Picks letter from dictionary
        std::uniform_int_distribution<> distribPos(0, alphabet.size() - 1);

        std::vector<std::string> ret;

        // generate random number of strings
        unsigned int numStrings = maxStrings;
        if ( !fixedSize)
           numStrings = distribStrings(gen);
           
        ret.resize(numStrings);

        for (unsigned int i = 0; i < numStrings; ++i)
        
            std::string& backStr = ret[i];
            // Generate size of string
            unsigned strSize = distribMinMax(gen);
            for (unsigned j = 0; j < strSize; ++j)
            
                // generate each character and append to string
                unsigned pickVal = distribPos(gen);
                backStr += alphabet[pickVal];
            
        
        return ret;
    
;

int main()

    StringFuzzer info;
    auto v = info.getFuzzData();  // produces a vector of strings, ready to be used in tests
    info.setAlphabet("ABCDEFG").setMinSize(1);  // alphabet consists only of these characters, and we will not have any empty strings
    v = info.getFuzzData();  // now should be a vector of random strings with "ABCDEFG" characters
    for (auto s : v)
       std::cout << s << "\n";

【讨论】:

我认为std::transform(Price.begin() + 1, Price.end(), Price.begin() + 1, [&amp;](int n) if (n &lt; 1) return distribPos(gen); return n; );这一行是错误的,我认为改变的条件是n == 0,否则,值-1可以一起改变。 还有一点就是这个问题必须是购买公斤数和购买包数之间的条件,或者包数不能大于公斤。所以我认为,虽然它可能是运行时错误的原因,但 SPOJ 上的编译器会排除这种测试用例 我将答案更新为设置n == 0。答案的重点实际上是展示如何回答 SPOJ 问题(或任何在线法官问题),查看约束条件,并在出现异常时生成任何测试数据。很多时候 *** 都会遇到这样的问题,在线法官抛出异常,不知道测试数据是什么。这基本上解决了这个问题。 @HoangNam -- 好的,如果您觉得 SPOJ 数据将是特定值,您仍然可以修复 PriceKN 值以适应随机后的特定标准已生成符合一般标准的数字。主要部分是生成大量测试数据,这就是随机数生成将为您做的事情。

以上是关于在 SPOJ 上提交代码会导致运行时错误 (SIGABRT)的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 SPOJ GCD2 代码在 SPOJ 上出错?

网页上有错误,无法提交

向 Google 表格提交表单时出错,导致 CORS 错误

重命名项目会导致 med 代码签名错误

在 Ionic 应用程序中使用 Vega Charts 会导致在某些设备上启动时出现运行时错误

git合并丢失代码问题分析与解决(错误操作导致)