将const char *缓存为返回类型

时间:2008-10-14 11:58:47

标签: c++ char return-value

在我的C ++上读了一下,发现这篇关于RTTI(运行时类型识别)的文章: http://msdn.microsoft.com/en-us/library/70ky2y6k(VS.80).aspx。好吧,这是另一个主题:) - 但是,我在type_info - 类中偶然发现了一个奇怪的说法,即::name - 方法。它说:“type_info::name成员函数返回一个const char*到一个以空字符结尾的字符串,表示该类型的人类可读名称。指向的内存是缓存的,永远不应该直接释放。”< / p>

你怎么能自己实现这样的东西!?我以前经常在这个问题上苦苦挣扎,因为我不想为调用者删除一个新的char - 数组,所以我坚持std::string因此远。

所以,为了简单起见,假设我想创建一个返回"Hello World!"的方法,让我们称之为

const char *getHelloString() const;

就个人而言,我会以某种方式(Pseudo):

const char *getHelloString() const
{
  char *returnVal = new char[13];
  strcpy("HelloWorld!", returnVal);

  return returnVal
}

..但这意味着调用者应该在我的返回指针上执行delete[] :(

提前谢谢

12 个答案:

答案 0 :(得分:23)

这个怎么样:

const char *getHelloString() const
{
    return "HelloWorld!";
}

直接返回文字意味着字符串的空间由编译器在静态存储中分配,并且在程序的整个过程中都可用。

答案 1 :(得分:3)

我喜欢关于如何静态分配字符串的所有答案,但对于所有实现来说并不一定如此,尤其是原始海报链接到其文档的那些实现。在这种情况下,看起来装饰类型名称是静态存储的,以节省空间,未修饰的类型名称按需计算并缓存在链表中。

如果您对Visual C ++ type_info::name()实现如何分配和缓存其内存感到好奇,那么就不难发现。首先,创建一个微小的测试程序:

#include <cstdio>
#include <typeinfo>
#include <vector>    
int main(int argc, char* argv[]) {
    std::vector<int> v;
    const type_info& ti = typeid(v);
    const char* n = ti.name();
    printf("%s\n", n);
    return 0;
}

构建它并在调试器下运行它(我使用WinDbg)并查看type_info::name()返回的指针。它是否指向全球结构?如果是这样,WinDbg的ln命令将告诉最接近的符号的名称:

0:000> ?? n
char * 0x00000000`00857290
 "class std::vector<int,class std::allocator<int> >"
0:000> ln 0x00000000`00857290
0:000>

ln没有打印任何内容,这表明该字符串不在任何特定模块所拥有的地址范围内。如果它在数据或只读数据段中,它将在该范围内。让我们看一下它是否在堆上分配,通过搜索所有堆来查找type_info::name()返回的地址:

0:000> !heap -x 0x00000000`00857290
Entry             User              Heap              Segment               Size  PrevSize  Unused    Flags
-------------------------------------------------------------------------------------------------------------
0000000000857280  0000000000857290  0000000000850000  0000000000850000        70        40        3e  busy extra fill 

是的,它是在堆上分配的。在malloc()的开头放置断点并重新启动程序会确认它。

查看<typeinfo>中的声明,可以了解堆指针缓存的位置:

struct __type_info_node {
    void *memPtr;
    __type_info_node* next;
};

extern __type_info_node __type_info_root_node;
...
_CRTIMP_PURE const char* __CLR_OR_THIS_CALL name(__type_info_node* __ptype_info_node = &__type_info_root_node) const;

如果找到__type_info_root_node的地址并在调试器中的列表中向下走,则会很快找到包含type_info::name()返回的相同地址的节点。该列表似乎与缓存方案有关。

原始问题中链接的MSDN页面似乎填写了空白:名称以其装饰形式存储以节省空间,此表单可通过type_info::raw_name()访问。当您在给定类型上第一次调用type_info::name()时,它会取消设计名称,将其存储在堆分配的缓冲区中,缓存缓冲区指针并返回它。

链接列表也可用于在程序退出期间解除分配缓存的字符串(但是,我没有验证是否是这种情况)。这将确保在运行内存调试工具时它们不会显示为内存泄漏。

答案 2 :(得分:2)

嗯,如果我们只谈论一个函数,那么你总是希望返回相同的值。这很简单。

const char * foo() 
{
   static char[] return_val= "HelloWorld!";
   return return_val;
}

棘手的一点是当你开始做缓存结果的事情,然后你必须考虑线程,或者你的缓存失效,并试图将东西存储在线程本地存储中。但如果它只是立即复制的一次性输出,这应该可以解决问题   或者,如果你没有固定的大小,你必须做一些事情,你必须使用任意大小的静态缓冲区...你可能最终有一个太大的东西,或转向托管类说{{1} }。

std::string

也是功能签名

const char * foo() 
{
   static std::string output;
   DoCalculation(output);
   return output.c_str();
}

仅适用于会员功能。 此时您不需要处理静态函数局部变量,只能使用成员变量。

答案 3 :(得分:1)

我认为,因为他们知道这些数量有限,所以他们只是永远保持它们。在某些情况下,您可能适合这样做,但作为一般规则,std :: string会更好。

他们也可以查找新的调用,看看他们是否已经创建了该字符串并返回相同的指针。同样,根据您的工作情况,这对您也很有用。

答案 4 :(得分:1)

在实现分配一块内存然后期望调用者释放它的函数时要小心,就像在OP中那样:

const char *getHelloString() const
{
  char *returnVal = new char[13];
  strcpy("HelloWorld!", returnVal);

  return returnVal
}

通过这样做,您将内存的所有权转移给调用者。如果从其他函数调用此代码:

int main()
{
  char * str = getHelloString();
  delete str;
  return 0;
}

...转移内存所有权的语义不明确,造成了更有可能出现错误和内存泄漏的情况。

此外,至少在Windows下,如果这两个函数位于2个不同的模块中,则可能会损坏堆。特别是,如果main()在hello.exe中,在VC9中编译,并且getHelloString()在utility.dll中,在VC6中编译,则在删除内存时会破坏堆。这是因为VC6和VC9都使用自己的堆,并且它们不是同一个堆,因此您从一个堆分配并从另一个堆中释放。

答案 5 :(得分:0)

为什么返回类型需要为const?不要将该方法视为 get 方法,将其视为 create 方法。我见过很多API,要求你删除创建操作符/方法返回的内容。请确保在文档中注明。

/* create a hello string
 * must be deleted after use
 */
char *createHelloString() const
{
  char *returnVal = new char[13];
  strcpy("HelloWorld!", returnVal);

  return returnVal
}

答案 6 :(得分:0)

当我需要这种功能时,我经常做的是在类中有一个char *指针 - 初始化为null - 并在需要时分配。

即:

class CacheNameString
{
    private: 
        char *name;
    public:
        CacheNameString():name(NULL)  { }

    const char *make_name(const char *v)
    {
        if (name != NULL)
            free(name);

        name = strdup(v);

        return name;
    }

};

答案 7 :(得分:0)

这样的事情可以做到:

const char *myfunction() {
    static char *str = NULL; /* this only happens once */
    delete [] str; /* delete previous cached version */
    str = new char[strlen("whatever") + 1]; /* allocate space for the string and it's NUL terminator */
    strcpy(str, "whatever");
    return str;
}
编辑:我发生的事情是,对此的一个很好的替代可能是返回一个boost :: shared_pointer。这样,调用者只要他们想要就可以抓住它,并且他们不必担心明确地删除它。公平妥协IMO。

答案 8 :(得分:0)

给出的建议是警告返回字符串的生命周期。在管理返回指针的生命周期时,您应该始终小心识别自己的责任。但是,如果指向的变量将比返回它的函数的调用更久,那么这种做法是非常安全的。例如,考虑c_str()作为类std::string的方法返回的const char指针。这将返回一个指向由字符串对象管理的内存的指针,只要字符串对象未被删除或重新分配其内部存储器,该指针就保证有效。

std::type_info类的情况下,它是C ++标准的一部分,因为它的命名空间意味着。从name()返回的内存实际上是指编译器和链接器在编译类时创建的静态内存,它是运行时类型标识(RTTI)系统的一部分。因为它引用代码空间中的符号,所以不应尝试删除它。

答案 9 :(得分:0)

我认为这样的事情只能使用对象和RAII成语“干净地”实现。 当调用对象析构函数(obj超出范围)时,我们可以安全地假设const char*指针不再被使用。

示例代码:

class ICanReturnConstChars
{
    std::stack<char*> cached_strings
    public:
    const char* yeahGiveItToMe(){
        char* newmem = new char[something];
        //write something to newmem
        cached_strings.push_back(newmem);
        return newmem;
    }
    ~ICanReturnConstChars(){
        while(!cached_strings.empty()){
            delete [] cached_strings.back()
            cached_strings.pop_back()
        }
    }
};

我所知道的另一种可能性是传递一个smart_ptr ..

答案 10 :(得分:-3)

可能是使用静态缓冲区完成的:

const char* GetHelloString()
{
    static char buffer[256] = { 0 };
    strcpy( buffer, "Hello World!" );
    return buffer;
}

此缓冲区就像一个只能从此函数访问的全局变量。

答案 11 :(得分:-5)

你不能依赖GC;这是C ++。这意味着您必须保持内存可用,直到程序终止。您根本不知道何时删除[]它是安全的。所以,如果你想构造并返回一个const char *,简单的new []它并返回它。接受不可避免的泄漏。