OpenGroup POSIX.1-2001 определяет strerror_r, как и Стандартная базовая базовая спецификация ядра Linux 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;
}
Из документов:
Базовые спецификации Open Group, выпуск 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 / строка / strerror.c: 41:
char *
strerror (errnum)
int errnum;
{
...
buf = malloc (1024);





Я бы не стал беспокоиться об этом - размера буфера 256 более чем достаточно, а 1024 - это слишком много. Вы можете использовать strerror() вместо strerror_r(), а затем, при желании, получить результат strdup(), если вам нужно сохранить строку ошибки. Однако это не является потокобезопасным. Если вам действительно нужно использовать strerror_r() вместо strerror() для обеспечения безопасности потоков, просто используйте размер 256. В glibc-2.7 самая длинная строка сообщения об ошибке составляет 50 символов («Недействительный или неполный многобайтовый или широкий символ»). Я бы не ожидал, что будущие сообщения об ошибках будут значительно длиннее (в худшем случае - на несколько байтов).
И если вам необходимо использовать символ, я бы предложил BUFSIZ из <stdio.h>
Чтобы прояснить, мне «действительно нужно использовать strerror_r () вместо strerror () для обеспечения безопасности потоков».
Обратите внимание, что сообщения могут быть в 3 раза длиннее в неанглийской локали только потому, что используемые в них символы выше U+0800. Для идеографических языков это вряд ли проблема, потому что, хотя каждый символ состоит из 3 байтов вместо 1 байта, слова часто состоят из 1-2 символов вместо 6-12 символов. Но на других языках, отличных от латинского алфавита (особенно в индийских скриптах), я мог видеть сообщения об ошибках, легко достигающие 256 байт.
Достаточно большого статического предела, вероятно, достаточно для всех ситуаций. Если вам действительно нужно получить все сообщение об ошибке, вы можете использовать Версия strerror_r для GNU или стандартную версию и опрашивайте его с последовательно увеличивающимися буферами, пока не получите то, что вам нужно. Например, вы можете использовать что-то вроде приведенного ниже кода.
#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 оригинала) или удваивать каждый раз ... тогда у вас будет алгоритм.
хотя желательность и надежность использования этого подхода вообще сомнительны. вероятно, просто определение подходящего размера фиксированного буфера выигрывает - по соображениям простоты и надежности.
Томас, о, я по ошибке использовал += вместо *=; фиксация.
Это отлично работает, спасибо. Это немного смешно, что нам даже нужно об этом беспокоиться. Кто бы ни определил интерфейс POSIX strerror_r, он задумался даже меньше, чем оригинальный strerror. Это пример плохого дизайна, отсутствия инкапсуляции.
Также не забудьте использовать временную переменную для этого перераспределения. В противном случае может произойти сбой, что приведет к потере исходного указателя.
Эта программа (запустить онлайн (как C++) здесь):
#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 даже может быть небезопасным для потоков. Эти возвращенные строки должны быть указателями на строковые литералы. (Пожалуйста, просветите меня, но я не могу придумать случая, когда бы они были синтезированы во время выполнения). Строковые литералы по определению доступны только для чтения, а доступ к данным только для чтения всегда потокобезопасен.
В комментариях к этот ответ есть некоторые обсуждения причин, по которым strerror не может быть потокобезопасным.
@CarstenS Спасибо. Я все еще считаю, что локали - плохая причина для отказа от повторного входа. У каждой локали может быть свой собственный набор статических строк, доступных только для чтения. Я предполагаю, что кто-то может захотеть синтезировать локализованные результаты из общих частей, но выполнение такого рода дедупликации может показаться мне крайним случаем преждевременной оптимизации, и это исходит от преждевременного оптимизатора. К сожалению, нет смысла спорить со стандартами.
Меня это тоже раздражает. Я думаю, что проблема, на которую намекают комментарии к ответу на этот другой вопрос, заключается в том, что локаль может быть деинсталлирована, пока у кого-то еще есть указатель на строку с ошибкой. Во всяком случае, тоже не мои решения;)
Обратите внимание, что вместо использования функции
strerrorвы можете использоватьsyslogсо спецификатором%m(которым является POSIX-совместимый). Пример:syslog(LOG_ERR, "Error occured, details: %m"). Прочтите руководствоsyslog, чтобы узнать больше. К сожалению, я не знаю, является ли%mпотокобезопасным, какstrerror_r.