【问题标题】:What size should I allow for strerror_r?strerror_r 应该允许多大的尺寸?
【发布时间】:2010-09-30 05:02:53
【问题描述】:

OpenGroup POSIX.1-2001 定义了strerror_rThe Linux Standard Base Core Specification 3.1 也是如此。但是我找不到对错误消息可以合理预期的最大大小的参考。我希望有一些定义可以放在我的代码中,但我找不到。

代码必须是线程安全的。这就是为什么使用 strerror_r 而不是 strerror。

有人知道我可以使用的符号吗?我应该创建自己的吗?


示例

int result = gethostname(p_buffy, size_buffy);
int errsv = errno;
if (result < 0)
{
    char buf[256];
    char const * str = strerror_r(errsv, buf, 256);
    syslog(LOG_ERR,
             "gethostname failed; errno=%d(%s), buf='%s'",
             errsv,
             str,
             p_buffy);
     return errsv;
}

来自文档:

开放组基本规范第 6 期:

错误

如果出现以下情况,strerror_r() 函数可能会失败:

  • [ERANGE] 通过 strerrbuf 和 buflen 提供的存储空间不足 包含生成的消息字符串。

来源:

glibc-2.7/glibc-2.7/string/strerror.c:41:

    char *
    strerror (errnum)
         int errnum;
    {
        ...
        buf = malloc (1024);

【问题讨论】:

  • 请注意,您可以使用syslog%m 说明符(即POSIX-compliant)来代替strerror 函数。示例:syslog(LOG_ERR, "Error occured, details: %m")。阅读syslog 手册以了解更多信息。不幸的是,我不知道 %m 是否像 strerror_r 那样是线程安全的。

标签: c linux standards posix glibc


【解决方案1】:

对于所有情况,具有足够大的静态限制可能就足够了。 如果确实需要获取完整的错误信息,可以使用GNU version of strerror_r,也可以使用标准版 并用连续更大的缓冲区轮询它,直到你得到你需要的东西。例如, 你可以使用类似下面的代码。

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Call strerror_r and get the full error message. Allocate memory for the
 * entire string with malloc. Return string. Caller must free string.
 * If malloc fails, return NULL.
 */
char *all_strerror(int n)
{
    char *s;
    size_t size;

    size = 1024;
    s = malloc(size);
    if (s == NULL)
        return NULL;

    while (strerror_r(n, s, size) == -1 && errno == ERANGE) {
        size *= 2;
        s = realloc(s, size);
        if (s == NULL)
            return NULL;
    }

    return s;
}

int main(int argc, char **argv)
{
    for (int i = 1; i < argc; ++i) {
        int n = atoi(argv[i]);
        char *s = all_strerror(n);
        printf("[%d]: %s\n", n, s);
        free(s);
    }

    return 0;
}

【讨论】:

  • 对于 GNU strerror_r,来自您提供的链接:(如果 buflen 太小,字符串可能会被截断)。这如何解决问题?
  • @Michael,如果缓冲区太小,符合 XSI 的 strerror_r 会返回错误。在我看来,GNU 的用处不大。
  • 每次增加 2 个字节的缓冲区大小并没有给我留下深刻的印象。尝试添加 256 字节(原始字节的 1/4)或每次加倍......然后你就会有一个算法。
  • 尽管使用这种方法的可取性和可靠性完全值得怀疑。可能只是定义一个适当大的固定缓冲区获胜——基于简单性和可靠性。
  • Thomas,哦,我误用了+= 而不是*=;修复。
【解决方案2】:

我不会担心 - 256 的缓冲区大小已经绰绰有余,而 1024 则过大了。如果需要存储错误字符串,您可以使用strerror() 代替strerror_r(),然后可以选择使用strdup() 结果。不过,这不是线程安全的。如果您确实需要使用strerror_r() 而不是strerror() 来保证线程安全,只需使用256 的大小即可。在glibc-2.7 中,最长的错误消息字符串为50 个字符(“无效或不完整的多字节或宽字符”)。我不希望未来的错误消息会更长(在最坏的情况下,会长几个字节)。

【讨论】:

  • 如果你必须使用符号,我建议来自 的 BUFSIZ
  • 澄清一下,我“确实需要使用 strerror_r() 而不是 strerror() 来保证线程安全”。
  • 请注意,在非英语语言环境中,消息的长度很容易增加 3 倍,因为它们使用的字符高于 U+0800。对于表意语言来说,这几乎不是问题,因为虽然字符是 3 字节而不是每个 1 字节,但单词通常是 1-2 个字符而不是 6-12 个字符。但在其他非拉丁字母语言(尤其是印度语脚本)中,我可以看到错误消息很容易达到 256 个字节。
【解决方案3】:

这个程序(run online (as C++) here):

#include <stdio.h>
#include <errno.h>
#include <string.h>

int main(){
        const int limit = 5;
        int unknowns = 0;
        int maxlen = 0;
        int i=0; char* s = strerror(i);
        while(1){
            if (maxlen<strlen(s)) maxlen = strlen(s);
            if (/*BEGINS WITH "Unknown "*/ 0==strncmp("Unknown ", s , sizeof("Unknown ")-1) )
                unknowns++;
            printf("%.3d\t%s\n", i, s);
            i++; s=strerror(i);
            if ( limit == unknowns ) break;
        }
        printf("Max: %d\n", maxlen);
        return 0;
}

列出并打印系统上的所有错误并跟踪最大长度。从外观上看,长度不超过 49 个字符(纯 strlen 没有最后的 \0),所以有一些余地,64-100 应该绰绰有余。

我很好奇是否不能简单地通过返回结构来避免整个缓冲区大小协商,以及是否存在不返回结构的根本原因。所以我进行了基准测试:

#define _POSIX_C_SOURCE 200112L //or else the GNU version of strerror_r gets used
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

typedef struct { char data[64]; } error_str_t;
error_str_t strerror_reent(int errn) __attribute__((const));
error_str_t strerror_reent(int errn){
    error_str_t ret;
    strerror_r(errn, ret.data, sizeof(ret));
    return ret;
}


int main(int argc, char** argv){
    int reps = atoi(argv[1]);
    char buf[64];
    volatile int errn = 1;
    for(int i=0; i<reps; i++){
#ifdef VAL
        error_str_t err = strerror_reent(errn);
#else
        strerror_r(errn, buf, 64);
#endif
    }
    return 0;
}

并且两者在 -O2 时的性能差异很小:

gcc -O2 : The VAL version is slower by about 5%
g++ -O2 -x c++ : The VAL version is faster by about 1% than the standard version compiled as C++ and by about 4% faster than the standard version compiled as C (surprisingly, even the slower C++ version beats the faster C version by about 3%).

无论如何,我认为strerror 甚至被允许成为线程不安全是非常奇怪的。那些返回的字符串应该是指向字符串文字的指针。 (请赐教,但我想不出应该在运行时合成它们的情况)。并且字符串字面量根据定义是只读的,并且对只读数据的访问始终是线程安全的。

【讨论】:

  • this answer的cmets中讨论了strerror可能不是线程安全的原因。
  • @CarstenS 谢谢。我仍然认为语言环境是不可重入的一个不好的理由。每个语言环境都可以简单地拥有自己的一组静态只读字符串。我想有人可能想从常见的子片段中合成本地化结果,但在我看来,这种重复数据删除似乎是过早优化的极端情况,而这来自过早优化器。不幸的是,与标准争论没有多大意义。
  • 我也觉得这很烦人。我认为 cmets 对暗示的其他问题的答案的问题是,当有人仍然有指向错误字符串的指针时,可能会卸载语言环境。无论如何,也不是我的决定;)
  • @PSkocik - 关于在线运行 C,你可以试试TIOideone
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-07
  • 1970-01-01
  • 2013-11-25
相关资源
最近更新 更多