Having a sufficiently large static limit is probably good enough for all situations. If you really need to get the entire error message, you can use the GNU version of strerror_r, or you can use the standard version and poll it with successively larger buffers until you get what you need. For example, you may use something like the code below.
#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;
}
I wouldn't worry about it - a buffer size of 256 is far more than sufficient, and 1024 is overkill. You could use strerror()
instead of strerror_r()
, and then optionally strdup()
the result if you need to store the error string. This isn't thread-safe, though. If you really need to use strerror_r()
instead of strerror()
for thread safety, just use a size of 256. In glibc-2.7
, the longest error message string is 50 characters ("Invalid or incomplete multibyte or wide character"). I wouldn't expect future error messages to be significantly longer (in the worst case, a few bytes longer).
This program (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;
}
lists and prints all the errors on the system and keeps track of the maximum length. By the looks of it, the length does not exceed 49 characters (pure strlen
's without the final \0
) so with some leeway, 64–100 should be more than enough.
I got curious if the whole buffer size negotiation couldn't simply be avoided by returning structs and whether there was a fundamental reason for not returning structs. So I benchmarked:
#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;
}
and the performance difference between the two at -O2 is minimal:
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%).
In any case, I think it's extremely weird that strerror
is even allowed to be thread unsafe. Those returned strings should be pointers to string literals. (Please enlighten me, but I can't think of a case where they should be synthesized at runtime). And string literals are by definition read only and access to read only data is always thread safe.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With