【问题标题】:Calling function twice gives segfault (in connection with char* to string conversion)两次调用函数会产生段错误(与 char* 到字符串的转换有关)
【发布时间】:2021-11-16 15:24:42
【问题描述】:

我想用 Windows 环境路径扩展一个字符串 ("%LOCALAPPDATA%/test.txt")。以下函数原则上可以完成这项工作,但是使用相同的输出字符串再次调用它(或在调用函数之前为输出字符串分配一些值)会产生段错误。

显然我通过将char* 转换为std::string 犯了一些(可能非常糟糕)错误,但我真的不明白这里发生了什么(除了一些内存地址稍后不可用的事实) .

#include "processenv.h"

void expandWindowsString(const std::string &input, std::string &output)
{
    const char* in = input.c_str();
    char* out;
    ExpandEnvironmentStrings(in, out, 1024);
    output = out;
}

int main(int argc, char *argv[])
{
    std::string path;
    expandWindowsString("%LOCALAPPDATA%/test.txt", path);
    std::cout << "path is " << path << std::endl;
    //works fine so far, but if I execute the function again (with 'path') or initialising path beforehand with std::string path = "", a segfault occurs.
    expandWindowsString("%LOCALAPPDATA%/test.txt", path); // commenting out this line, makes the code work.
    std::cout << "path is " << path << "\n";
}

【问题讨论】:

  • ExpandEnvironmentStrings 是什么?
  • @463035818_is_not_a_number: expandenvironmentstringsa
  • char* out; -&gt; char out[1024];
  • 很遗憾,你第一次尝试也是UB,但是好像可以。

标签: c++ string winapi segmentation-fault char


【解决方案1】:

正如documentation 解释的那样:

lpDst

一个指向缓冲区的指针,它接收扩展lpSrc缓冲区中的环境变量字符串的结果。

缓冲区需要由调用者提供,而代码只是传递一个未初始化的指针,同时诱使系统相信它指向大小为 1024 字节的内存。

一个简单的解决方法是:

void expandWindowsString(const std::string &input, std::string &output)
{
    const char* in = input.c_str();
    char out[1024];
    ExpandEnvironmentStrings(in, out, 1024);
    output = out;
}

还有很多改进的空间,例如

  • 使用Unicode version
  • 处理错误(如文档中所述)
  • 重复增大输出缓冲区以防 API 调用返回的值大于提供的缓冲区长度
  • 使用pointer and length 构造std::string
  • 返回值而不是使用输出参数

至于为什么第二次调用失败:这是一个毫无意义的问题。代码通过写入未初始化的指针表现出未定义的行为。这样,任何结果都是可能的,包括看起来按预期工作的代码。

【讨论】:

    【解决方案2】:

    ExpandEnvironmentStrings 需要一个已分配的缓冲区。如果you pass a buffer with a size too small to hold the string,可以确定缓冲区的大小,比如0。

    所以,这里有两个版本,UNICODE 和 ANSI,它们将动态计算大小:

    std::wstring expandWindowsString(const std::wstring& input)
    {
        std::wstring output;
        do
        {
            auto size = ExpandEnvironmentStringsW(input.c_str(), (LPWSTR)output.c_str(), output.size());
            if (!size || size <= output.size())
                break;
    
            output.resize(size);
        } while (true);
        output = output.c_str();
        return output;
    }
    
    std::string expandWindowsString(const std::string& input)
    {
        std::string output;
        do
        {
            auto size = ExpandEnvironmentStringsA(input.c_str(), (LPSTR)output.c_str(), output.size());
            if (!size || size <= output.size())
                break;
    
            output.resize(size);
        } while (true);
        output = output.c_str();
        return output;
    }
    

    【讨论】:

    • 一些需要解决的问题:1 从 C++17 开始,data() 返回一个指向非常量的指针,从而允许删除 const_cast2 reserve() 不会修改 size()。该实现无条件地返回一个空字符串。请改用resize(),然后再使用另一个resize() 来删除尾随的NUL 字符。
    • 3 环境是进程全局状态,任何线程都可以随时更改。一个健壮的实现需要重复调​​用 API,直到它返回一个值 &gt; 0&lt;= output.size()
    • @IInspectable - 对于 2,我使用的是 MSVC,所有 C++ 版本,它似乎工作正常,但我同意使用调整大小更好。
    • 冒着过于迂腐的风险,我仍然建议进行以下两个更改:1 添加另一个调用 resize() 以去除尾随的 NUL 字符,这仍然是受控序列的一部分std::string 的。 2 不那么美观的变化:当 API 返回 0 时,确实应该报告为错误。 std::system_error 将是一个不错的候选人。另外,如果您可以将我的回答中解决问题的部分复制到此答案中,那将是一个非常好的问答。
    • @IInspectable - 实际上,ANSI 版本放置了两个奇怪的终止零(这已记录在案)。至于错误,我更喜欢返回一个空字符串而不是引发错误,这是我个人的设计,是我个人的选择。
    【解决方案3】:

    如果您阅读doc

    lpDst

    一个指向缓冲区的指针,它接收在 lpSrc 缓冲区中扩展环境变量字符串的结果。请注意,此缓冲区不能与 lpSrc 缓冲区相同。

    nSize

    lpDst 参数指向的缓冲区中可以存储的最大字符数。使用 ANSI 字符串时,缓冲区大小应为字符串长度加上终止空字符再加一。使用 Unicode 字符串时,缓冲区大小应该是字符串长度加上终止的空字符。

    你会看到你传递了无效的参数,你必须提供buffer,所以:

    void expandWindowsString(const std::string &input, std::string &output)
    {
        char out[1024];
        ExpandEnvironmentStrings(input.c_str(), out, sizeof (out));
        output = out;
    }
    

    或避免输出参数:

    std::string expandWindowsString(const std::string &input)
    {
        char out[1024];
        ExpandEnvironmentStrings(input.c_str(), out, sizeof (out));
        return out;
    }
    

    请注意,您可能应该检查ExpandEnvironmentStrings 的返回值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-26
      • 1970-01-01
      • 2016-02-20
      • 1970-01-01
      • 1970-01-01
      • 2020-12-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多