【问题标题】:Is this using CString with heap or stack? How do I use it with heap memory?这是将 CString 与堆或堆栈一起使用吗?如何将它与堆内存一起使用?
【发布时间】:2014-02-11 12:56:59
【问题描述】:

我正在使用 VS2012。我宁愿从堆中为 CString 分配内存,所以给出下面的代码:

  1. CString csToken 是从 stack 还是 heap 分配内存?
  2. 我需要释放 csToken 正在使用的内存,还是会在函数终止时自动释放?

    TCHAR *sAllCodes = (TCHAR *) calloc(50000,sizeof(TCHAR));       // Dynamically use Heap memory to hold all pipe-delimited codes
    TCHAR *sCode = (TCHAR *) calloc(128,sizeof(TCHAR)); // Dynamically use Heap memory to hold a single code
    DWORD i = 0;
    
    LoadAllCodes(&sAllCodes);       // Custom function
    
    CString csToken;        // Declare the CString variable
    csToken.Append(sAllCodes);  // Assign the string to the Cstring variable
    vector<CString> vAllCodes;  // Declare the vector variable
    vAllCodes = Split(csToken,L"|");        // Split the CString into a vector array
    
    while ( i < (DWORD) vAllCodes.size())
        {
        if (vAllCodes[i].StringLength() > 0)        // If there is a string in the vector item
            {
            _stprintf_s(sCode,128,L"%s",vAllCodes[i].GetString());      // Get the vector item and copy it into a string
    
            // Do work on sCode here...
            }
        i++;
        }
    
    free(sAllCodes);sAllCodes=NULL;
    free(sCode);sCode=NULL;
    

【问题讨论】:

    标签: c++ memory-management heap-memory c-strings stack-memory


    【解决方案1】:

    您的csToken 变量是分配在堆栈 上的CString 的一个实例,因此您无需执行任何操作来删除它:它的析构函数 将当这个变量的作用域终止时,做适当的字符串清理。

    但是,CString 在内部 维护对实际字符串的引用,该字符串分配在 上。
    CString 实际上使用 "COW" 技术和引用计数,所以几个CString 实例可以共享同一个字符串,它们可以引用同一个字符串。但是,这些是CString实现细节

    CString 的基本用法的“要点”是,如果您有 CString str,则不必注意 str 清理:它将被自动管理CString析构函数。


    此外,我想借此机会对您的代码做一些笔记,本着改进它的精神。

    使用std::vector

    您在发布的代码顶部有这些分配:

    TCHAR *sAllCodes = (TCHAR *) calloc(50000,sizeof(TCHAR));
    TCHAR *sCode = (TCHAR *) calloc(128,sizeof(TCHAR));
    

    然后你在底部有相应的免费电话:

    free(sAllCodes);sAllCodes=NULL;
    free(sCode);sCode=NULL;
    

    但请注意,此代码不是 异常安全:事实上,如果calloc()free() 调用之间的任何代码碰巧引发C++ 异常,在堆上为sAllCodessCode 分配的内存将不会被释放。

    更现代更实用的 C++ 风格中,您可以使用 std::vector 而不是 calloc() 来分配内存堆,并自动释放它,感谢std::vector析构函数(就像CString!)。

    只需将 calloc() 调用替换为:

    std::vector<TCHAR> allCodes(50000);
    std::vector<TCHAR> code(128);
    

    然后删除free() 调用! :)
    除了更简单、更短之外,您的代码也变得异常安全;事实上,如果抛出 C++ 异常,vector 析构函数会自动被调用,并释放分配的内存。

    如果要访问vector数据,可以使用其data()成员函数。

    如果要将初始向量内容设置为全0,可以使用vector v(count, 0)构造函数重载。

    使用 C++ 风格的强制转换

    在您的代码中,您有一个 C 风格的 (DWORD) 演员表。 C 风格的转换是不好的;考虑改用 C++ 风格的强制转换。在您的情况下,您可能需要static_cast&lt;DWORD&gt;(...)

    使用CString::IsEmpty() 提高可读性

    您的代码中有这样的 if:

    if (vAllCodes[i].StringLength() > 0)
    

    CString 提供了一个方便的IsEmpty() 方法,更具可读性:

    if (! vAllCodes[i].IsEmpty()) 
        ....
    

    关于_stprintf_s() 和“幻数”的注释

    你像这样使用_stprintf_s()

    _stprintf_s(sCode,128,L"%s",vAllCodes[i].GetString()); 
    

    但请注意,如果您使用 TCHAR 模型,为了保持一致性,您应该在格式说明符中使用 _T("%s")
    如果你只想使用 Unicode 字符串和 L"%s"(我建议),那么使用对应的 Unicode-only swprintf_s(),并摆脱 TCHAR 并改用 wchar_t

    还要注意,您可能想要使用sCode.size(),而不是使用“幻数”128(假设sCode 成为std::vector 的一个实例,正如我上面所建议的) .所以,如果你改变向量的大小,你也不必更新“幻数”128(有了这些幻数,基本上会有错误等待发生:)
    更新后的代码可能类似于:

    swprintf_s(code.data(), code.size(), L"%s", vAllCodes[i].GetString()); 
    

    【讨论】:

    • 酷!那么我如何使用向量执行 swprintf_s、_tcscpy_s、_tcscat_s?
    • 另外,编译器不就是用 sw* 代替 _st* 函数,所以最终结果不是速度损失?
    • @JeffR:我已经在我的示例中展示了这一点:使用vector::data()vector::size()。至于您的第二条评论,是的,这只是预处理器的问题:没有速度损失。
    • 我现在明白了!执行“new TCHAR(n)”将 n 限制为 65535。向量是否有限制?还是使用向量的速度惩罚?
    • @JeffR:我不明白你写的限制。 std::vector 内存分配的限制是可用内存的限制。我认为如果vector 未能分配其内存,则会引发std::bad_alloc 异常。至于速度惩罚,如果您使用vector::operator[] 重载(例如v[i]),这与原始数组一样有效(如果您使用vector::at() 方法会有一个小惩罚,因为它会进行边界检查在 Visual C++ 中的调试和发布版本中)。
    【解决方案2】:
    1. CString csToken 变量是在堆栈上分配的,但是一旦分配了某个字符串,它就会在堆上分配其内部缓冲区。

    2. 你不需要显式释放csToken占用的任何内存,一旦csToken变量超出范围,它就会被释放,并调用它的析构函数。

    【讨论】:

    • 1.那么一旦给 csToken 分配了一个字符串,栈上的内存分配就全部被堆内存代替了?
    • 没有。将 CString 视为一个包含指针和长度(ptr 和 l)的类。最初两者都是 0。一旦分配了 CString 值,就会分配堆内存,并将字符串放置在那里。 ptr 指向这个内存,l 等于字符串长度。 ptr 和 l 仍在堆栈中。
    • 顺便说一句,CString 可以在堆上分配自己的资源这一事实对您来说是透明的 - 您无需对此做任何事情。
    • 好的,我明白了。我是在这段代码中以最有效的方式做我的 CString,还是有更好的方法? (顺便说一句,我没有使用 MFC,这是一个控制台应用程序 [服务],代码顶部带有“#include ”,因此我可以使用 CString)
    • 我不能说整个算法,但是在这种情况下使用局部变量CString csToken; 是绝对可以的。在 C++ 中使用 calloc 有点不寻常,最好使用 new,正如 Cygwinnian 已经提到的那样。
    【解决方案3】:

    一旦函数超出范围,分配在堆栈上的任何内容都将被释放。堆上的任何东西都必须显式释放。

    csToken 没有被new 关键字分配,所以它在堆栈上,而不是在堆上。

    最后我看到你用的是C++,在C++中使用free()malloc()是非常忌讳的。您应该使用newdelete 关键字。

    编辑:

    这一行可以改写:

    TCHAR *sAllCodes = (TCHAR *) calloc(50000,sizeof(TCHAR));
    

    作为:

    TCHAR *sAllCodes = new TCHAR(5000);
    

    这是新关键字的使用方法。

    【讨论】:

    • 如何将 malloc/free 更改为 new/delete?任何为什么它是禁忌?似乎工作正常?
    • CString 类用于存储字符串的实际内存分配在堆上。一旦csToken 超出范围,就会调用解构函数,您无需担心手动释放内部缓冲区。
    • TCHAR *sAllCodes = new TCHAR[5000]; ... delete[] sAllCodes;
    • 显然,我只能将 65535 (int) max 放入'new TCHAR[x]'!编译器返回“warning C4305: 'initializing' : truncation from 'int' to 'TCHAR'”和“warning C4309: 'initializing' : truncation of constant value”
    猜你喜欢
    • 1970-01-01
    • 2018-08-30
    • 2016-09-25
    • 2014-07-08
    • 2018-11-23
    • 2016-12-14
    • 2021-09-06
    • 2017-03-12
    • 1970-01-01
    相关资源
    最近更新 更多