关于C#:strerror_r应该允许多大?

What size should I allow for strerror_r?

OpenGroup POSIX.1-2001定义了strerror_r,Linux标准基础核心规范3.1也是如此。但是我找不到错误消息可以合理预期的最大大小的引用。我希望可以在代码中定义一些地方,但是找不到。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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期:

ERRORS

The strerror_r() function may fail if:

  • [ERANGE] Insufficient storage was supplied via strerrbuf and buflen to
    contain the generated message string.

来源:

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

1
2
3
4
5
6
    char *
    strerror (errnum)
         int errnum;
    {
        ...
        buf = malloc (1024);


具有足够大的静态限制可能足以满足所有情况。
如果确实需要获取完整的错误消息,则可以使用GNU版本的strerror_r,也可以使用标准版本。
并使用依次更大的缓冲区对其进行轮询,直到获得所需的内容为止。例如,
您可以使用类似下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#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, s);
        free(s);
    }

    return 0;
}


我不会担心-256的缓冲区大小远远不够,而1024则过大。如果需要存储错误字符串,则可以使用strerror()而不是strerror_r(),然后使用strdup()结果。但是,这不是线程安全的。如果确实需要使用strerror_r()而不是strerror()来确保线程安全,则使用256的大小。在glibc-2.7中,最长的错误消息字符串为50个字符("无效或不完整的多字节或宽字符")。我不希望将来的错误消息会更长(在最坏的情况下,会长几个字节)。


该程序(在此处在线运行(作为C)):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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\
"
, i, s);
            i++; s=strerror(i);
            if ( limit == unknowns ) break;
        }
        printf("Max: %d\
"
, maxlen);
        return 0;
}

列出并打印系统上的所有错误,并跟踪最大长度。从外观上看,该长度不超过49个字符(不带最后一个\\0的纯strlen),因此在留有余地的情况下,64a€100应该足够了。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#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两者之间的性能差异很小:

1
2
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甚至被允许是线程不安全的,这非常奇怪。这些返回的字符串应该是指向字符串文字的指针。 (请启发我,但是我想不起来应该在运行时综合它们的情况)。根据定义,字符串文字是只读的,对只读数据的访问始终是线程安全的。