【问题标题】:Undefined behaviour with function returning pointer函数返回指针的未定义行为
【发布时间】:2018-07-03 05:36:33
【问题描述】:

编辑:问题不是未定义的行为,而是字符数组的“误用”

我在指针和动态内存分配方面工作不多,所以我决定尝试使用这些方法制作一个非常简单的加密器。 (这个加密器不应该很好,它使用凯撒方法,只有 +1 而不是 3,并且在字母之间使用符号使其更难解密,这里不是批评算法) em>

我认为我面临的问题是未定义的行为,但我真的不明白这是怎么发生的。假设我想加密“Hello”,它只打印字母表中“H”之后的“I”,但它停在那里并且程序变得无响应,所以我认为问题出在else 部分for 循环。编译器还会警告我堆损坏,但在查看此页面后,我认为我正确地释放了内存。

#include <iostream>
#include <string>
#include <ctime>

using namespace std;

char * enc(string);

int main()
{
    string foo = "Hello";

    char * bar = enc(foo);

    cout << *bar;

    delete[] bar;

    cin.get();

    return 0;
}

char * enc(string str)
{
    char * encrypted = new char[int(str.size())];
    srand(unsigned int(time(NULL)));

    // Disguise symbols for obscurifying text
    char * disg = new char[37]
    {
        //37 symbols, unrelevant and takes a lot of space.
    };

    for (int i = 0; i < str.size(); i++)
    {
        encrypted[i] = int(str[i]) + 1;
        if (i == str.size())
            break;
        else
            encrypted[i + 1] = disg[rand() % 37];
    }

    delete[] disg;

    return encrypted;
}

作为旁注,我确实意识到向量可能更适合此目的,但我还没有深入了解,这是为了练习内存管理。

【问题讨论】:

  • @Someprogrammerdude 不,我不完全明白为什么这是 UB 我自己,我所说的无响应是它在“我”之后停止,我无法点击进入关闭程序(这是我使用cin.get() 的),关闭它的唯一方法是通过任务管理器,或者VS中的停止按钮
  • 它永远不会进入 if (i == str.size()) ,因为您在 for 循环中声明了 i
  • 与您的问题有些无关:您为什么使用原始字符指针? enc 函数应该返回 string 而不是 char*
  • @MichaelWalz 我知道这看起来很奇怪,我也不想这样做,但正如我在 Werner Henze 的回答中所说的那样“我起初厌倦了 enc 返回字符串,但我不知道如何初始化一个动态大小的字符串,所以我选择了 char。”
  • 我也不想用大量问题(我确实有)让这篇文章超载,因为它变得太不确定和太宽泛了,我想我可以尝试自己解决其余的问题或单独制作一个,关于这些事情的更深入的问题

标签: c++ memory undefined-behavior


【解决方案1】:

但我不知道如何用动态大小初始化字符串

我已经获取了您的代码并对其进行了修改,以使用 std::string 并删除了分配,因为这里根本不需要它们。

请阅读代码、调试它并检查 cmets。

class Enc
{
public:
    //For arrays of fixed size, you may use this to obtain the size of the given array
    template <typename T, std::size_t N>
    constexpr static std::size_t FixedArraySize(T(&arr)[N])
    {
        return N;
    }

    //The parameter should not be modified, pass it as constant value by reference
    static std::string enc(const std::string &str)
    {
        //It looks like your symbols do not change, let's make them static to initialize only once
        static char symbols[] =
        {
            'A', 'x', '!' //...
        };

        auto originalStringSize = str.size();

        //Create the destination string and resize it to the source string's length
        std::string encrypted;
        encrypted.resize(originalStringSize);

        //Your loop
        for (int i = 0; i < originalStringSize; ++i)
        {
            //Make safe cast, which is also obvious to the reader of the code
            encrypted[i] = static_cast<int>(str[i]) + 1;

            //Fixed with - 1 to break correctly
            if (i == (originalStringSize - 1))
                break;
            else
                encrypted[i + 1] = symbols[rand() % FixedArraySize(symbols)]; //Notice the helper function
        }

        //Return the 'encrypted' string
        return encrypted;
    }
};

int wmain(int, wchar_t**)
{
    std::string testString = "Hello World";

    //Call srand only once in your program !
    srand(static_cast<unsigned int>(time(nullptr)));
    auto encrypted = Enc::enc(testString);

    std::cout << encrypted << std::endl;

    return 0;
}

作为旁注,我确实意识到向量可能更适合此目的,但我还没有深入了解,这是为了练习内存管理。

您可以使用std::vector,但在这里也不是必需的。在现代 C++ 中,您很少需要处理原始指针。

【讨论】:

  • 虽然内存分配是整个项目的重点,但我非常感谢这个答案,它让我更好地理解了如何规划和执行代码。
  • 我有 1 个问题,您是否有任何具体原因选择 wmain() 而不是 main() 或者这只是一般的好习惯?
  • @Chaost 我使用 Unicode 字符集而不是多字节作为项目设置。例如检查这个问题:stackoverflow.com/questions/3064052/…
【解决方案2】:

让我们仔细看看enc函数中的循环:

for (int i = 0; i < str.size(); i++)
{
    encrypted[i] = int(str[i]) + 1;
    if (i == str.size())
        break;
    else
        encrypted[i + 1] = disg[rand() % 37];
}

循环将使用i0 迭代到str.size() - 1(含)。这意味着循环内的条件i == str.size()永远为真,并且您将在最后一次迭代中在else 子句中写入越界。 p>

解决方法是将if中的条件改为i == str.size() - 1


还有一些其他的问题,但没有一个会像上面那样导致 UB。例如,您写入encrypted[i + 1] 的值将在下一次迭代中被覆盖。

【讨论】:

  • 我已经进行了这些更改并且它有点工作,但这至少回答了我的问题!我还注意到它会覆盖符号,所以我也修复了它。现在只有一些怪癖发生,但我想我可以解决这个问题。这里的两个答案都是正确且有帮助的,但这解释了我如何修复代码而不是进行我不完全理解的更改,所以我会继续这样做。
【解决方案3】:
cout << *bar;

正在打印 bar 指向的第一个字符。

你想要

cout << bar;

但为此,您需要 null 终止返回的字符串。

旁注:请不要使用原始指针。 disg 应该是例如 std::array。而enc 应该返回一个std::string

if (i == str.size())can never be true inside your loop. Also as Some programmer dude says in his answer you are writing out of bounds in the lineencrypted[i + 1] = disg[rand() % 37];。但是如果你删除了错误的 if,你会更好地看到这一点。

for (int i = 0; i &lt; str.size(); i++) 可以重写为for(auto c : str)

【讨论】:

  • 起初我厌倦了enc 返回字符串,但我不知道如何初始化具有动态大小的字符串,所以我选择了 char。当我变得更好时,我可能会尝试改进此功能,谢谢您的帮助。
  • @Chaost string (size_t n, char c);string::resize 甚至更简单:将结果字符串初始化为输入字符串的副本。在你的情况下str不是const,所以你可以修改str然后返回修改后的str。
猜你喜欢
  • 2020-01-12
  • 2014-05-03
  • 1970-01-01
  • 1970-01-01
  • 2019-08-22
  • 2013-04-10
  • 1970-01-01
  • 2021-10-10
  • 1970-01-01
相关资源
最近更新 更多