【问题标题】:Reasonable snprintf-like alternatives to strftime?strftime 的类似 snprintf 的合理替代方案?
【发布时间】:2017-07-06 10:37:38
【问题描述】:

是否有任何标准(C、C++、POSIX、Linux...)替代 strftime 可用于

  1. 计算给定格式和时间所需的字符串缓冲区大小,
  2. 如果缓冲区大小小于完整输出所需的大小,则截断输出(而不是像 strftime 那样保留未定义的数组内容)。

例如,接受strftime 格式字符串的日期/时间格式的类似snprintf 的语义将非常适合。

C++11 及更高版本中的 std::put_time 等函数不是一个选项,因为它们可能会尝试动态分配额外的内存并可能引发异常。

【问题讨论】:

  • 假设在接下来的 7983 年有人会更新你的代码,然后数一数你需要多少字节来格式化你手指上的时间。似乎没有必要自动执行此操作。避免使用长的星期和月份名称,因为它们取决于语言环境。
  • 不,没有,但您可以相对轻松地编写自己的。 stdarg.hvsnprintf 可以帮助实现这一目标。
  • @Akira 好吧,根据当前的语言环境等,当涉及到工作日和月份名称时,这可能不是那么容易。替换不需要 stdarg 和 vsnprintf,因为 strftime 有固定数量的论据。
  • 为什么动态内存分配有问题?这很容易:分配一个缓冲区,如果失败,尝试 strftime(),增加(加倍)缓冲区大小并迭代。如果你想避免 malloc()&co,你甚至可以在堆栈上使用 VLA
  • 是的,512 字节在我能想到的任何语言中都应该足够长。

标签: c++ c date-formatting strftime


【解决方案1】:

可以继续尝试更大的缓冲区,直到代码成功(或认为这太多了)。下面使用 VLA(不是 C++),偷偷地避免“尝试动态分配额外的内存” - 眨眼眨眼。

简单地分配一个大缓冲区,比如char buf[J_STRFTIME_MAX],对于实际编码应该足够了。 @Michaël Roy 并避免使用迭代方法。

#include <stdio.h>
#include <time.h>
#define J_STRFTIME_MAX 100

size_t j_strftime(char * s, size_t maxsize, const char * fmt, const struct tm * t) {
  size_t sz = strftime(s, maxsize, fmt, t);
  if (sz) {
    return sz;
  }
  size_t new_size = maxsize ? maxsize : 1;
  do {
    new_size *= 2;
    char new_s[new_size];
    sz = strftime(new_s, sizeof new_s, fmt, t);
    if (sz) {
      s[0] = 0;
      // strncat(s, new_s, maxsize);
      strncat(s, new_s, maxsize - 1);
      return strlen(new_s);
    }
  } while (sz < J_STRFTIME_MAX/2);
  return 0;
}

int main() {
  time_t now;
  time(&now);
  struct tm tm = *gmtime(&now);
  for (size_t i = 1; i < 30; i += 3) {
    char s[i];
    size_t sz = j_strftime(s, sizeof s, "%c", &tm);
    printf("%2zu %2zu <%s>\n", i, sz, s);
  }
}

输出

 1 24 <T>
 4 24 <Thu >
 7 24 <Thu Jul>
10 24 <Thu Jul  6>
13 24 <Thu Jul  6 14>
16 24 <Thu Jul  6 14:45>
19 24 <Thu Jul  6 14:45:00>
22 24 <Thu Jul  6 14:45:00 20>
25 24 <Thu Jul  6 14:45:00 2017>
28 24 <Thu Jul  6 14:45:00 2017>

非迭代

size_t j_strftime2(char * s, size_t maxsize, const char * fmt, const struct tm * t) {
  size_t sz = strftime(s, maxsize, fmt, t);
  if (sz == 0) {
    char new_s[J_STRFTIME_MAX];
    sz = strftime(new_s, sizeof new_s, fmt, t);
    if (sz == 0) {
      return 0;  // Too too big
    }
    s[0] = 0;
    // strncat(s, new_s, maxsize);
    strncat(s, new_s, maxsize - 1);
  }
  return sz;
}

[编辑] 代码已更正。

【讨论】:

  • 为避免不必要的迭代,您可以从 new_size 的较大值开始:size_t new_size = maxsize &gt; 128 ? maxsize : 128;
  • strftime 可能由于目标缓冲区大小之外的其他原因而失败,在这种情况下,您的方法肯定会有未定义的行为。
  • @chqrlie 是的。一个很好的直接方法。然而 OP 似乎隐含着对内存使用的担忧,因此迭代方法逐渐兴起。
  • 为什么不在所有情况下都返回sz,与snprintf 具有相同的语义?用户将在最多 2 个步骤中分配最小大小(在调用者级别)。
  • @chqrlie 同意返回所需的大小(不包括 \0)。修改了代码。
猜你喜欢
  • 2021-12-05
  • 1970-01-01
  • 2019-12-10
  • 1970-01-01
  • 1970-01-01
  • 2016-09-30
  • 2012-12-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多