什么意外行为可以返回指向char数组成员的指针?

时间:2014-10-27 17:43:10

标签: c++ arrays class

好的,好的。我一直在研究一个类项目(我们还没有涵盖std :: string和std :: vector,但显然我知道它们)来构建一个时钟。程序的主要部分需要时间和日期值作为格式化的c字符串(例如“12:45:45”,“12/12/12”等),我可能通过存储它们来保持简单我的基础课的方式。但是,我没有。

相反,我这样做了:

class UsageEntry {
public:
    ....

    typedef     time_t  TimeType;
    typedef     int     IDType;

    ...
    // none of these getters are thread safe
    // furthermore, the char* the getters return should be used immediately
    // and then discarded: its contents will be modified on the next call
    // to any of these functions.

    const char*         getUserID();
    const char*         getDate();
    const char*         getTimeIn();
    const char*         getTimeOut();

private:
    IDType      m_id;
    TimeType    m_timeIn;
    TimeType    m_timeOut;

    char        m_buf[LEN_MAX];
};

其中一个吸气剂(它们基本上都是一样的):

const char* UsageEntry::getDate()
{
    strftime(m_buf, LEN_OF_DATE, "%D", localtime(&m_timeIn));
    return m_buf;
}

这是一个使用这个指针的函数:

// ==== TDataSet::writeOut ====================================================
// writes an entry to the output file
void TDataSet::writeOut(int index, FILE* outFile)
{
    // because of the m_buf kludge, this cannot be a single
    // call to fprintf
    fprintf(outFile, "%s,", m_data[index].getUserID());
    fprintf(outFile, "%s,", m_data[index].getDate());
    fprintf(outFile, "%s,", m_data[index].getTimeIn());
    fprintf(outFile, "%s\n", m_data[index].getTimeOut());
    fflush(outFile);
} // end of TDataSet::writeOut

这会造成多少麻烦?或者从另一个角度来看它,还有什么其他有趣的东西!!有趣!这会导致什么行为?最后,可以做些什么来解决它(除了使用字符串/向量的明显解决方案)?

有点相关:做类似事情的C ++库函数如何处理这个问题?例如localtime()返回一个指向struct tm对象的指针,该对象以某种方式在该函数调用结束时存活至少足够长,以供strftime使用。

3 个答案:

答案 0 :(得分:2)

没有足够的信息来确定它是否会引起麻烦,因为您没有说明如何使用它。只要您记录警告并在使用课程时牢记这些警告,就不会有问题。

需要注意一些常见问题,但希望这些是常识:

  • 删除UsageEntry将使getter返回的指针无效,因为这些缓冲区也将被删除。 (如果在MadScienceDream's example中使用本地声明的UsageEntry,则特别容易遇到这种情况。)如果存在风险,则调用者应创建自己的字符串副本。记下这一点。

  • m_timeIn似乎不是const,因此可能会发生变化。调用getter将修改内部缓冲区,并且具有该指针的任何内容都可以看到这些更改。如果这是一个风险,调用者应该创建自己的字符串副本。记下这一点。

  • 您的获取者既不是可重入的也不是线程安全的。记下这一点。

让调用者提供目标缓冲区和长度作为参数会更安全。为方便起见,该函数可以返回指向该缓冲区的指针。这就是例如read有效。

强大的API可以避免问题。如果做不到这一点,良好的文档和常识也可以减少问题的发生。行为只有在没有人期望的情况下才会出乎意料,这就是有关行为的文档很重要的原因:它通常会消除意外行为。

将其视为烤箱顶部的“小心:热表面”警告。您可以在顶部设计带有绝缘层的烤箱,这样就不会发生意外。如果做不到这一点,你至少可以做一个警告标签,可能不会出现意外。如果既没有绝缘也没有警告,最终有人自行燃烧。


既然您已经编辑了问题以在标题中显示一些文档,那么许多初始风险已经降低。这是一个很好的改变。


以下是如果使用用户提供的缓冲区(以及返回指向该缓冲区的指针)如何更改使用情况的示例:

// ==== TDataSet::writeOut ====================================================
// writes an entry to the output file
void TDataSet::writeOut(int index, FILE* outFile)
{
    char userId[LEN_MAX], date[LEN_MAX], timeIn[LEN_MAX], timeOut[LEN_MAX];
    fprintf(outFile, "%s,%s,%s,%s\n",
        m_data[index].getUserID(userId, sizeof(userId)),
        m_data[index].getDate(date, sizeof(date)),
        m_data[index].getTimeIn(timeIn, sizeof(timeIn)),
        m_data[index].getTimeOut(timeOut, sizeof(timeOut))
    );
    fflush(outFile);
} // end of TDataSet::writeOut

答案 1 :(得分:2)

这里的主要问题是大多数基于指针的代码的主要问题是所有权问题。问题如下:

  const char* val;
  {
     UsageEntry ue;
     val = ue.getDate();
  }//ue goes out of scope
  std::cout << val << std::endl;//SEGFAULT (maybe, really nasal demons)

因为val实际上归ue所有,所以如果它们存在于不同的范围内,你就可以自己动手。你可以记录这个,但是将缓冲区作为参数传递是非常简单的(就像strftime函数那样)。

(感谢下面的odedsh指出这个)

另一个问题是随后的通话会打消所获得的信息。使用的示例odesh是

fprintf(outFile, "%s\n%s",ue.getUserID(), ue.getDate());

但问题更为普遍:

const char* id = ue.getUserID();
const char* date = ue.getDate();//Changes id!

这违反了“最小惊讶的原则”,因为它很奇怪。

这种设计也打破了每个班级应该完成一件事的经验法则。在这种情况下,UsageEntry都提供访问器以将格式化的时间作为字符串,并管理该字符串缓冲区。

答案 2 :(得分:2)

  

这会造成多少麻烦?或者从另一个角度来看,   还有什么其他的有趣和!!有趣!这会导致什么行为?   最后,可以做些什么来解决它(除了明显的解决方案   使用字符串/向量代替)?

这里没有什么特别好玩的,只是意味着你的getter的结果不能比UsageEntry的相应实例更长,或者你有一个悬空指针。

  

做类似事情的C ++库函数如何处理这个?   例如localtime()返回一个指向struct tm对象的指针   不知怎的,这个函数调用的结束至少存活了很长时间   由strftime使用。

The documentation of localtime says

  

返回值

     

成功时指向静态内部std :: tm对象的指针,否则为NULL。结构可以在之间共享   std :: gmtime,std :: localtime和std :: ctime,可能会被覆盖   每次调用。