为啥将字符串传递给接受 LPSTR 的函数不起作用?

Posted

技术标签:

【中文标题】为啥将字符串传递给接受 LPSTR 的函数不起作用?【英文标题】:why passing string to a function which accepts LPSTR does noy work?为什么将字符串传递给接受 LPSTR 的函数不起作用? 【发布时间】:2020-11-26 15:37:56 【问题描述】:

以下代码给出了空字符串和长度 = 0,但在调试时我可以看到 childDisplayName 的名称正确。

    CHAR fileSystemName[MAX_PATH + 1] =  0 ; 
    DWORD serialNumber = 0; DWORD maxComponentLen = 0; 
    string childDisplayName = "";
    DWORD fileSystemFlags = 0; 
    if (GetVolumeInformationA("C:\\", // L"\\MyServer\MyShare\"
        (LPSTR)&childDisplayName, MAX_PATH+1,
        &serialNumber, &maxComponentLen,
        &fileSystemFlags, fileSystemName, sizeof(fileSystemName)) == true)
    
        
        cout << childDisplayName << "length: "<<childDisplayName.length()<<endl;
    


以下代码可以正常工作。我不明白为什么 LPSTR 在传递 char 数组时有效,而在传递字符串时无效。

    CHAR fileSystemName[MAX_PATH + 1] =  0 ; 
    DWORD serialNumber = 0; DWORD maxComponentLen = 0; 
    CHAR childDisplayName[MAX_PATH + 1] =  0 ;
    DWORD fileSystemFlags = 0; 
    if (GetVolumeInformationA("C:\\", // L"\\MyServer\MyShare\"
        childDisplayName, MAX_PATH+1,
        &serialNumber, &maxComponentLen,
        &fileSystemFlags, fileSystemName, sizeof(fileSystemName)) == true)
    
        
        cout << childDisplayName << "length: "<<strlen(childDisplayName)<<endl;
    

【问题讨论】:

字符串的地址不是字符的地址,std::string只管理一个动态的字符数组。 @churill 感谢您的回复。但在第一个 sn-p 中,它打印空输出和长度 = 0。但是当我调试代码时,我可以在变量 childDisplayName 中看到正确的驱动器标签。你能解释一下吗? 每次转换都意味着,您正在回避 C++ 类型系统。结果是,编译器将不再验证正确性。验证正确性由您自己负责,但您没有这样做。 您好,如果任何答案对您有所帮助,请随时标记它以帮助遇到相同问题的人,如果您有任何问题,请告诉我。谢谢。 【参考方案1】:

string childDisplayName = ""; 创建一个空的空字符串(零大小和未指定的容量)。使用它作为数据缓冲区写入不太可能顺利。

您可以这样做:string childDisplayName(MAX_PATH + 1, ' '); 创建一个分配了适当空间的字符串。

其次,正如@churill 所写,字符串的地址不是其中字符的地址。而是使用childDisplayName.data()char* 获取到您可以写入的字符串的内部存储空间 - 但请确保不要写入[data(); data() + size()) 范围之外。

编辑:关于std::string.data() 的工作原理。

我做了一个小例子程序:

#include<iostream>
#include<string>

void print(const std::string& s)

    std::cout << "String size: " << s.size() << '\n';
    std::cout << "String contents: >>" << s << "<<\n";
    std::cout << "String as c-string: >>" << s.c_str() << "<<\n";
    std::cout << '\n';


int main() 
    std::string bla = "";
    auto bladata = bla.data();
    for (int i = 0;i < 5;++i) 
        bladata[i] = '!';
    
    print(bla);

    std::string bla2(10, '\0');
    auto bla2data = bla2.data();
    for (int i = 0;i < 5;++i) 
        bla2data[i] = '!';
    
    print(bla2);

运行此输出时:

String size: 0
String contents: >><<
String as c-string: >>!!!!!╠╠╠╠╠╠╠╠╠╠╠<<

String size: 10
String contents: >>!!!!!     <<
String as c-string: >>!!!!!<<

这里发生了什么?首先要注意的是,一个空的std::string 是用零大小创建的和未指定的容量 - 查看我的调试器,我知道在我的系统上未指定的容量是 15,所以只要我不这样做'不要超越那没有什么应该崩溃。但这显然不是你在真实代码中应该做的事情(在这里写是严格未定义的行为)。

这意味着bla 字符串的大小为 0,并包含一个 15 个字符的字符缓冲区,其中我将前 5 个字符设置为“!”。因此,当我尝试打印其size() 或将其打印为std::string 时,它与任何常规空字符串相同。但是,如果我使用.c_str() 直接打印内部缓冲区,那么它会像任何旧的char* 一样打印,并且只会打印内存中的任何内容,直到遇到空字符。

另一方面,bla2 被初始化为包含 10 个空字符。这意味着它的大小是 10,它的容量是至少 10(在我的例子中它恰好也是 15)。这意味着在循环之后它仍然报告大小为 10,无论我将多少个 '!' 放入缓冲区,当我将其打印为 std::string 时,它会打印它包含的所有 10 个字符; 5 '!' 和 5 '\0'。但是,当我将其打印为 char* 时,它会打印 5 个“!”,然后在遇到空字符时立即停止。

【讨论】:

在第一个代码 sn-p 中,我尝试发送 childDisplayName.c_str() 它不起作用(输出中的长度 = 0)。但是在第二个 sn-p 中,当我使用 childDisplayName (这是 char 数组)时。它工作正常。我不明白这两个有什么不同?为什么 LPSTR 为这 2 个提供不同的行为? 同样在第一个 sn-p 中,它打印空输出并且长度也是 0。但是当我调试代码时,我可以在变量 childDisplayName 中看到正确的驱动器标签。你能解释一下吗 c_str() 返回一个 const,因此您不能将其传递给期望 LPSTR 的 arg。使用data(),如答案所示。 我使用了string childDisplayName (MAX_PATH + 1, ' ');(LPSTR)childDisplayName.data(),它成功了。但是我可以看到.data() 也返回const char*,那么它怎么会在这里工作呢?我很困惑.. @aromahola -- 您应该意识到,字符串很可能是使用短字符串优化实现的,其中短字符串存储在数组中。也许您正在查看内部数组?无论如何,您要使用std::stringpublic 接口,而不是依赖于字符串类的实现方式。【参考方案2】:

在第一个代码 sn-p 中,我无法获得 childDisplayName 的正确名称。

虽然好像有几个字符,但是想输出的时候触发了异常。

所以你给GetVolumeInformationA传递了错误的地址。因为字符串的地址不是字符的地址

你可以像下面的代码一样测试:

string s("test");
CHAR cs[] = "test";
cout << (void*)&s << endl;
cout << (void*)&s[0] << endl;

cout << (void*)&cs << endl;
cout << (void*)&cs[0] << endl;

输出:

正如@churill 所说,std::string 只管理一个动态的字符数组。所以你不能将字符串的地址传递给函数。

并根据MSDN:

如果修改数据的 const 重载返回的字符串的内容,则行为未定义。如果将终端空字符更改为任何其他值,您也会得到未定义的行为。如果对字符串的非常量引用传递给标准库函数,则返回的指针可能无效。也可以通过调用非常量成员函数来使其无效。

所以即使你真的成功地将childDisplayName.data() 传递给函数。这也是一种未定义的行为。我建议你正确传入函数所需的参数类型,而不是试图传递其他未定义或无类型的行为,这会给你带来很大的困惑。

【讨论】:

以上是关于为啥将字符串传递给接受 LPSTR 的函数不起作用?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的代码在传递给 .exe 时不起作用?

为啥我的 to_string() 不起作用? [复制]

将redisContext传递给另一个函数时,libhiredis不起作用

试图将 ViewController 中的字符串传递给 NSObject 中的字符串 .. 但它不起作用

使用 Parse 方法 getObjectInBackgroundWithId 将 objectId 从 viewDidLoad 传递给另一个函数不起作用

将参数从 servlet 传递给函数 Javascript,查看数据库时它不起作用?