Как улучшить определение размера буфера для печати различных целочисленных типов?


При преобразовании целого числа в текст, как правило, я создаю большой буфер для использования с sprintf() для хранения любого потенциального результата.

char BigBuffer[50];
sprintf(BugBuffer, "%d", SomeInt);

Я бы хотел, чтобы пространство было более эффективным и, безусловно, портативным, поэтому вместо 50, нашел альтернативу:
(sizeof(integer_type)*CHAR_BIT*0.302) + 3

// 0.0302 about log10(2)
#define USHORT_DECIMAL_BUFN ((size_t) (sizeof(unsigned short)*CHAR_BIT*0.302) + 3)
#define INT_DECIMAL_BUFN    ((size_t) (sizeof(int)           *CHAR_BIT*0.302) + 3)
#define INTMAX_DECIMAL_BUFN ((size_t) (sizeof(intmax_t)      *CHAR_BIT*0.302) + 3)

int main() {
    char usbuffer[USHORT_DECIMAL_BUFN];
    sprintf(usbuffer, "%hu", USHRT_MAX);
    printf("Size:%zu Len:%zu %sn", sizeof(usbuffer), strlen(usbuffer), usbuffer);

    char ibuffer[INT_DECIMAL_BUFN];
    sprintf(ibuffer, "%d", INT_MIN);
    printf("Size:%zu Len:%zu %sn", sizeof(ibuffer), strlen(ibuffer), ibuffer);

    char imbuffer[INTMAX_DECIMAL_BUFN];
    sprintf(imbuffer, "%" PRIdMAX, INTMAX_MIN);
    printf("Size:%zu Len:%zu %sn", sizeof(imbuffer), strlen(imbuffer), imbuffer);
    return 0;
}

Size:7 Len:5 65535
Size:12 Len:11 -2147483648
Size:22 Len:20 -9223372036854775808

Итак, вопросы таковы:
1 Есть ли проблема с альтернативным уравнением?
2 Какое лучшее решение? - поскольку эта альтернатива немного расточительна и выглядит чрезмерно сложный.

[Edit Ответ ]

Ответы обеспечивают 3 продуманных подхода:
1 использовать буфер [максимальный размер для типа] (выбран ответ)
2 asprintf()
3 snprintf()

1 максимальный размер буфера времени компиляции с использованием уравнения (sizeof(integer_type)*CHAR_BIT*0.302) + 3 не был нарушен или улучшен. Влияние <locale.h> было исследовано в соответствии с предложением @paddy, и никакие настройки локали не повлияли на целочисленные преобразования %d %x %u %i. Было обнаружено, что небольшое улучшение может быть внесено в уравнение, если тип известен как подписанный или неподписанный (ниже). @ paddy предостережение о "более консервативном" - хороший совет.

2 asprintf() это действительно хорошее универсальное решение, но не портативное. Может быть, в пост-С11?

3 snprintf(), несмотря на стандартность, известны последовательные проблемы реализации, когда поставляемый буфер имеет малый размер. Это подразумевает вызов его с буфером большого размера, а затем создание буфера нужного размера. @jxh предложил потокобезопасный глобальный скретч-буфер для формирования ответа с локальным буфер нужного размера. Этот новый подход заслуживает рассмотрения, которое я могу использовать, но первоначальный вопрос больше сосредоточен на определении перед вызовом s(n)printf() консервативного размера буфера.

signed ((sizeof(integer_type)*CHAR_BIT-1)*0.302) + 3
unsigned (sizeof(integer_type)*CHAR_BIT*0.302) + 2
*28/93 может использоваться вместо *0.302.

4 3

4 ответа:

По-моему, это хорошо. Вы округлили десятичную дробь, добавили дополнительный символ для отрицательного знака и нуля, а также один дополнительный для хорошей меры. Я не думаю, что вам нужно беспокоиться о числах, которые выходят дольше, если вы не используете функции из <locale.h>.

Мой вопрос заключается в том, что вы собираетесь с ними делать. Вы просто строите их в стеке, или вы помещаете их много в память?

С временными массивами в стеке вы обычно не суетитесь из-за несколько байт, так как это вряд ли повлияет на производительность кэша. Это, конечно, не уничтожит твою память.

Если вы планируете хранить их много,вы можете рассмотреть возможность объединения. Тем не менее, вам нужно будет учитывать накладные расходы на память при объединении в пул. Сама природа пула означает, что вы сохраняете больше памяти, чем собираетесь использовать. А если компиляция 64-битная, то ваши указатели составляют 8 байт. Если большинство ваших чисел имеют длину 4 символа, то 8-байтовый указатель плюс 5 байт хранение каждого числа сводит на нет все возможные преимущества, за исключением, возможно, 64-разрядных чисел.

Это всего лишь мои мыслительные процессы. Мне кажется, что вы хорошо подстригли жир. Я склонен быть немного более консервативным, но это может быть в основном паранойя. Обычно все делается просто, и чрезмерное мышление может оказаться ловушкой. Если вы слишком много думаете, то подумайте о причинах этого и решите, действительно ли эта проблема требует такого внимания.

Asprintf() удобен, он берет символ ** и использует malloc (), чтобы получить необходимое пространство, поэтому вам нужно освободить его позже.

Не нужно беспокоиться о том, сколько места вам нужно.

int asprintf(char **ret, const char *format, ...); 

char *p
asprintf(&p, "%XXXX", ...); 
:
:
free(p);

Вот схема, расширяющая мой предыдущий комментарий. Вы используете INTMAX_DECIMAL_BUFN в качестве наихудшего размера буфера и используете его для печати с snprintf(). Значение, возвращаемое параметром snprintf(), используется для объявления VLA, который точно соответствует размеру массива, необходимому для печатаемой строки, и эта строка копируется в VLA.

#define INTMAX_DECIMAL_BUFN ((size_t) (sizeof(intmax_t)*CHAR_BIT*0.302) + 3)

char numstr[INTMAX_DECIMAL_BUFN];

int main () {
    int n = snprintf(numstr, sizeof(numstr), "%hu", USHRT_MAX);
    char usbuffer[n+1];
    strcpy(usbuffer, numstr);
    printf("Size:%zu Len:%zu %s\n", sizeof(usbuffer), strlen(usbuffer), usbuffer);
}

Если потокобезопасность является проблемой, переменная numstr может быть сделана потокобезопасной (с помощью C. 11 _Thread_local или определенного расширения компилятора, подобного GCC __thread).

Значение это решение зависит от того, стоит ли экономия пространства стека дополнительных вычислений для выполнения strcpy(). Если большинство ваших чисел, использующих большие целочисленные типы, На самом деле принимают значения намного меньшие, чем max, то этот метод может обеспечить вам значительную экономию (в зависимости от того, сколько массивов вы создаете).

Это прекрасно.

Я разработал оригинальную функцию snprintf() (в *BSD, которая в конечном итоге превратилась в C99), чтобы возвращать количество символов, которые были бы напечатаны, если бы буфер был достаточно большим. Если у вас есть соответствующий snprintf(), вы можете выполнить печать дважды, причем первый скажет вам, сколько места нужно выделить (Вы должны добавить один для завершения '\0', конечно). Это имеет два очевидных недостатка: он должен сделать форматирование дважды, и это вводит возможность проблемы синхронизации, когда первый вызов изменяет что-то (например, запись через директиву %n), так что второй вызов производит другой вывод.

К сожалению, существуют несовместимые реализации snprintf(), где это все равно не работает. [Edit: он работает для использования в ответе jxh , где вы предоставляете большой буфер; неудачный случай-это когда вы предоставляете слишком маленький буфер, чтобы узнать, сколько места вам нужно.]