是否可以*安全地*从函数中返回 TCHAR*?

Posted

技术标签:

【中文标题】是否可以*安全地*从函数中返回 TCHAR*?【英文标题】:Is it possible to *safely* return a TCHAR* from a function? 【发布时间】:2010-09-16 20:58:47 【问题描述】:

我创建了一个将所有事件通知代码转换为字符串的函数。真的很简单。

我有一堆像

这样的常量
const _bstr_t DIRECTSHOW_MSG_EC_ACTIVATE("A video window is being activated or deactivated.");
const _bstr_t DIRECTSHOW_MSG_EC_BUFFERING_DATA("The graph is buffering data, or has stopped buffering data.");
const _bstr_t DIRECTSHOW_MSG_EC_BUILT("Send by the Video Control when a graph has been built. Not forwarded to applications.");
.... etc....

和我的功能

TCHAR* GetDirectShowMessageDisplayText( int messageNumber )

    switch( messageNumber )
    
        case EC_ACTIVATE: return DIRECTSHOW_MSG_EC_ACTIVATE;
        case EC_BUFFERING_DATA: return DIRECTSHOW_MSG_EC_BUFFERING_DATA;
        case EC_BUILT: return DIRECTSHOW_MSG_EC_BUILT;
... etc ...

没什么大不了的。我花了 5 分钟才拼凑起来。

...但我根本不相信我已经获得了所有可能的值,所以如果没有找到匹配项,我希望默认返回类似“意外通知代码 (7410)”的内容。

不幸的是,无论如何我都想不出返回一个有效指针,而不强制调用者删除字符串的内存......这不仅令人讨厌,而且与其他返回值的简单性相冲突。

所以如果不将返回值更改为用户传入缓冲区和字符串长度的参数,我想不出任何方法来做到这一点。这会让我的函数看起来像

BOOL GetDirectShowMessageDisplayText( int messageNumber, TCHAR* outBuffer, int bufferLength )

    ... etc ...

我真的不想那样做。一定有更好的办法。

有吗?

在中断 10 年后我将回到 C++,所以如果这是显而易见的事情,请不要小看我忽略了它是有原因的。

【问题讨论】:

我不太清楚您要返回的消息是静态常量还是动态生成的。如果是前者,似乎没有问题,你可以直接返回指向它们的指针,因为它们永远不需要被释放。 【参考方案1】:

C++? std::string。它不会破坏任何现代计算机的性能。

但是,如果您需要对此进行过度优化,您有以下三种选择:

    使用示例中的缓冲区。 之后让用户删除该字符串。很多类似这样的 API 都提供了自己的删除函数,用于删除各种动态分配的返回数据。 返回一个指向静态缓冲区的指针,您在每次调用时用返回字符串填充该缓冲区。但是,这确实有一些缺点,因为它不是线程安全的,并且可能会令人困惑,因为返回的指针的值会在下次有人调用该函数时发生变化。如果非线程安全是可以接受的,并且您记录了这些限制,那应该没问题。

【讨论】:

如果他只想返回一个指向字符串常量的指针,那就太过分了。 std::string 在这里?即使这是 C++,这听起来也不好。在这里返回一个动态分配的字符串是没有意义的。 我更喜欢 std::string 而不是不安全地使用指针 - 慢速和正确比快速和错误更好。无论哪种方式,它都可能足够快 @Vlad:但这个问题是关于 HFT 系统的吗? @jalf:我没有说 std::string 不是线程安全的。这是矫枉过正。至于延迟,内存分配可能是个问题。【参考方案2】:

如果您要返回一个指向字符串常量的点,调用者将不必删除该字符串 - 他们只需要在您 new-ing 使用的内存时每次串。如果您只是返回一个指向错误消息表中的字符串条目的指针,我会将返回类型更改为TCHAR const * const,您应该没问题。

当然,这不会阻止您的代码的用户尝试删除指针引用的内存,但您可以做的只有这么多来防止滥用。

【讨论】:

谢谢。问题是,虽然我可以为所有已知的 messageNumber 返回 const TCHAR*,但我需要生成的默认值需要以某种方式分配。这就是我遇到问题的原因。 默认是固定字符串吗?如果是这样的话,那么最简单的方法可能是 Zack 的建议。【参考方案3】:

只需声明使用静态字符串作为默认结果:

TCHAR* GetDirectShowMessageDisplayText( int messageNumber )

  switch( messageNumber )
  
     // ...
     default:
       static TCHAR[] default_value = "This is a default result...";
       return default_value;
  

您也可以在函数之外声明“default_value”。

更新:

如果您想在该字符串中插入消息编号,那么它将不是线程安全的(如果您使用多个线程)。但是,该问题的解决方案是使用thread-specific 字符串。以下是使用Boost.Thread 的示例:

#include <cstdio>
#include <boost/thread/tss.hpp>

#define TCHAR char // This is just because I don't have TCHAR...

static void errorMessageCleanup (TCHAR *msg)

    delete []msg;


static boost::thread_specific_ptr<TCHAR> errorMsg (errorMessageCleanup);

static TCHAR *
formatErrorMessage (int number)

    static const size_t MSG_MAX_SIZE = 256;
    if (errorMsg.get () == NULL)
        errorMsg.reset (new TCHAR [MSG_MAX_SIZE]);
    snprintf (errorMsg.get (), MSG_MAX_SIZE, "Unexpected notification code (%d)", number);
    return errorMsg.get ();


int
main ()

    printf ("Message: %s\n", formatErrorMessage (1));

此方案的唯一限制是返回的字符串不能由客户端传递给其他线程。

【讨论】:

谢谢,我考虑过,但我无法将 messageNumber 插入字符串中。 @John:抱歉,我没有注意到您想在该字符串中输入一个数字。我已经相应地更新了我的答案。【参考方案4】:

也许有一个静态字符串缓冲区,您可以返回一个指向的指针:

std::ostringstream ss;
ss << "Unexpected notification code (" << messageNumber << ")";
static string temp = ss.str(); // static string always has a buffer
return temp.c_str(); // return pointer to buffer

这不是线程安全的,如果你持续持有返回的指针并用不同的messageNumbers 调用它两次,它们都指向temp 中的同一个缓冲区——所以两个指针现在都指向同一个消息。解决方案?从函数返回 std::string - 这是现代 C++ 风格,尽量避免使用 C 风格的指针和缓冲区。 (看起来你可能想发明一个tstring,这将是 ANSI 中的 std::string 和 unicode 中的 std::wstring,尽管我建议只使用 unicode ......你真的有任何理由支持非-unicode 构建?)

【讨论】:

这可能是一个可接受的限制,具体取决于较大的代码试图做什么。 @Vlad 我在提到“如果您从多个线程调用”时暗示了这一点 - 已编辑以澄清。【参考方案5】:

您返回某种自释放智能指针或您自己的自定义字符串类。您应该遵循 std::string 中定义的接口以便于使用。

class bstr_string 
    _bstr_t contents;
public:
    bool operator==(const bstr_string& eq);
    ...
    ~bstr_string() 
        // free _bstr_t
    
;

在 C++ 中,除非有重要原因,否则您永远不会处理原始指针,您总是使用自管理类。通常,Microsoft 使用原始指针是因为他们希望其接口与 C 兼容,但如果您不在乎,请不要使用原始指针。

【讨论】:

正如我在回答中指出的那样,_bstr_t 已经完成了这项工作 - 请参阅 MSDN。【参考方案6】:

简单的解决方案似乎只是返回一个std::string。它确实意味着一个动态内存分配,但在任何情况下您都可能会得到它(因为用户或您的函数必须明确地进行分配)

另一种方法可能是允许用户传入您将字符串写入其中的输出迭代器。然后用户可以完全控制如何以及何时分配和存储字符串。

【讨论】:

【参考方案7】:

在第一轮我错过了这是一个 C++ 问题,而不是一个普通的 C 问题。使用 C++ 开辟了另一种可能性:可以告知是否删除的自我管理指针类。

class MsgText : public boost::noncopyable

   const char* msg;
   bool shouldDelete;

public:
   MsgText(const char *msg, bool shouldDelete = false)
     : msg(msg), shouldDelete(shouldDelete)
   
   ~MsgText()
   
     if (shouldDelete)
       free(msg);
   
   operator const char*() const
   
     return msg;
   
;

const MsgText GetDirectShowMessageDisplayText(int messageNumber)

  switch(messageNumber)
  
    case EC_ACTIVATE:
      return MsgText("A video window is being activated or deactivated.");
    // etc
    default: 
      char *msg = asprintf("Undocumented message (%u)", messageNumber);
      return MsgText(msg, true);
    
  

(我不记得 Windows CRT 是否有 asprintf,但如果没有的话,在 std::string 之上重写上面的内容很容易。)

但请注意 boost::noncopyable 的使用 - 如果您复制这种对象,您将面临双重释放的风险。不幸的是,这可能会导致从您的 message-pretty-printer 函数返回它时出现问题。我不确定处理这个问题的正确方法是什么,我实际上并不是 C++ 大师。

【讨论】:

这里相同:正如我在回答中指出的那样,_bstr_t 已经完成了这项工作 - 请参阅 MSDN。【参考方案8】:

你已经使用了_bstr_t,所以如果你可以直接返回:

_bstr_t GetDirectShowMessageDisplayText(int messageNumber);

如果您需要在运行时构建不同的消息,您也可以将其打包到 _bstr_t 中。有了 RAII,现在所有权已经明确,使用仍然很简单。 开销可以忽略不计(_bstr_t 使用引用计数),调用代码仍然可以使用 _bstr_ts 转换为 wchar_t*char*(如果需要)。

【讨论】:

【参考方案9】:

这里没有好的答案,但这个杂牌可能就足够了。

const char *GetDirectShowMessageDisplayText(int messageNumber)

  switch(messageNumber)
  
     // ...
     default: 
       static char defaultMessage[] = "Unexpected notification code #4294967296";
       char *pos = defaultMessage + sizeof "Unexpected notification code #" - 1;
       snprintf(pos, sizeof "4294967296" - 1, "%u", messageNumber);
       return defaultMessage;
     
  

如果您这样做,调用者必须意识到他们从 GetDirectShowMessageText 返回的字符串可能会被随后对该函数的调用破坏。显然,它不是线程安全的。但这些可能是您的应用程序可以接受的限制。

【讨论】:

我认为这是一个完全有效的解决方案,并且我自己一直都在使用它。 (不过,说真的,每次只需让缓冲区 100 个字符和 snprintf 整个事情 - 更容易!)如果你愿意,你可以使用 TLS 来获得一些线程安全和/或一组缓冲区(使用循环)做一些事情,比如在另一个 printf 中多次调用函数等等。 是的,我一直在努力避免这种打击。 @Vlad:你有没有错过我说的“这不是线程安全的”部分?线程安全并非始终是一项要求。

以上是关于是否可以*安全地*从函数中返回 TCHAR*?的主要内容,如果未能解决你的问题,请参考以下文章

从char/wchar_t到TCHAR

TCHAR 仍然相关吗?

如何优雅地忽略 MATLAB 函数的某些返回值

C语言 TCHAR的字符串,怎么复制和比较?

从 C 中的函数返回一个 `struct`

VS2010中类似与MessageBox()的函数是啥?