Моя собственная реализация snprintf в C

Я решил сделать свою собственную версию snprintf в C. Я намеренно изменил некоторые вещи. Моя версия гарантирует, что напечатанный буфер будет завершен с нулевой отметкой, и он вернет количество символов, напечатанных в буфере, а не номер, который был бы напечатан, если размер буфера не был ограничен. И я беспокоился только о некоторых основных функциях форматирования, таких как %s, %c, %d, %h и %H.

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

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

int INT_TO_STR_DIGITS_L[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
int INT_TO_STR_DIGITS_U[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

int int_to_str(int x, char *buf, size_t size, int base, int uppercase) {
    int length = (int)ceil(log((double)x)/log((double)base));
    int r, i = 0;
    char c;

    if (size < length) {
        x /= (int)pow(base, (float)(length - size));
        length = size;
    }

    do {
        if (i >= size) break;
        r = x % base;
        if (uppercase) {
            c = INT_TO_STR_DIGITS_U[r];
        } else {
            c = INT_TO_STR_DIGITS_L[r];
        }
        buf[length-i-1] = c;
        x /= base;
        i++;
    } while (x != 0);

    return i;
}

int my_snprintf(char *str, size_t max_size, const char *fmt, ...) {

    va_list arg_list;
    va_start(arg_list, fmt);

    int chars_printed = 0;
    char *start_str = str;

    char c, *str_arg;
    int num, len;
    int uppercase = 0, base = 10;

    for (int i = 0; fmt[i] != 0; i++) {
        if (max_size - chars_printed <= 0) {
            break;
        } else if (fmt[i] == '%') {
            i++;
            switch (fmt[i]) {
            case 'c':
                c = va_arg(arg_list, int);
                str[chars_printed++] = c;
                break;
            case '%':
                str[chars_printed++] = '%';
                break;
            case 's':
                str_arg = va_arg(arg_list, char *);
                len = strnlen(str_arg, max_size - chars_printed);
                strncpy(str+chars_printed, str_arg, len);
                chars_printed += len;
                break;
            case 'H':
                uppercase = 1;
            case 'h':
                base = 16;
            case 'd':
                num = va_arg(arg_list, int);
                len = int_to_str(num, str+chars_printed, max_size - chars_printed, base, uppercase);
                chars_printed += len;
                break;
            default:
                printf("Invalid format.\n");
                va_end(arg_list);
                return -1;
            }
        } else {
            str[chars_printed++] = fmt[i];
        }
    }

    if (chars_printed == max_size) chars_printed--;
    str[chars_printed] = 0;

    va_end(arg_list);
    return chars_printed;
}
8 голосов | спросил addison 23 J0000006Europe/Moscow 2016, 18:12:17

2 ответа


8

Ошибка

Преобразование int в строку не работает правильно, когда напечатанное число является точной мощностью базы. Вот программа, которая демонстрирует ошибку:

int main(void)
{
    char buf[256];

    memset(buf, 'z', 256);
    my_snprintf(buf, 256, "abc%ddef", 1000);
    printf("%s\n", buf);
}

Ожидаемый результат:

abc1000def

Фактический выход:

ab1000zdef

Как вы можете видеть, часть 1000 была написана слишком далеко влево. Проблема в том, что вычисление длины номера отключено на единицу для точной мощности базы.

Ненужные и небезопасные операции с плавающей запятой

Полиция плавающей запятой ™ хотела бы указать, что использование плавающей запятой в int_to_str() как ненужные, так и опасные. Прежде всего, эта строка:

int length = (int)ceil(log((double)x)/log((double)base));

можно переписать, чтобы использовать цикл для подсчета количества цифр. Используя плавающие точки, вы открываете себя до ошибок округления. Например, если x были 125 и base было 5, вы ожидали бы, что length будет 3. Однако, когда я использовал приведенный выше код, используя 125 и 5 на моей машине x86, Вместо этого я получил length of 4. Это связано с тем, что деление, оцениваемое примерно на 3.00000001 и ceil, округлоло его до 4. (Конечно, уже есть не связанное с одним ошибка, упомянутая в предыдущем разделе. Это использование с плавающей запятой является отдельной причиной для беспокойства).

То же самое относится к этой строке:

    x /= (int)pow(base, (float)(length - size));

Это можно было бы переписать в виде цикла, в котором вы делите на base один раз за итерацию цикла. Используя pow() и перейдя на int, вы рискуете получить результат pow, ошибочно округляя до предыдущего int.

ответил JS1 23 J0000006Europe/Moscow 2016, 22:14:01
4
  1. Ошибка: int_to_str() сбой для отрицательного int

  2. Неопределенные спецификаторы: %h и %H не входят в стандартную библиотеку. Поэтому без спецификации трудно понять, правильно ли они выполняются. Вы имели в виду %x и %X?

  3. Смешивание int и size_t math , Это педантичная точка. Поскольку максимальный размер этих двух типов не указан, который больше, существует худший случай max_size - chars_printed <= 0 будет never be true if max_size > INT_MAX. Предложите добавить строку ** ниже и избежать математики, которая полагается на подписанную математику, так как это, вероятно, неподписанная математика. или используйте size_t chars_printed и справитесь с возвратом int в конце. (chars_printed должен иметь тип с большим положительным диапазоном.)

    int my_snprintf(char *str, size_t max_size, const char *fmt, ...) {
      if (max_size > INT_MAX) Handle_PathologicalCase_TBD(); // **
      int chars_printed = 0;
        ...
        // if (max_size - chars_printed <= 0) {
        if (max_size <= chars_printed) {
    
  4. Ошибка int length = (int)ceil(log((double)x)/log((double)base)); не такая надежная, как ожидалось. Подробный отчет в другом ответе . Альтернативой является преобразование в строку с внутренним максимальным буфером типа char buf[34] для int32_t в базе 2. Затем скопируйте результат буфера.

  5. Стиль: проваливается. Случаи, которые выпадают, хотя выглядят как ошибка без break. Добавить комментарий, чтобы показать намерение

        case 'H':
            uppercase = 1;
            // fall though
        case 'h':
            base = 16;
            // fall though
        case 'd':
    
  6. printf("Invalid format.\n"); и такие лучше печатаются на stderr

    fprintf(stderr, "Invalid format.\n");
    
  7. Неверный тип для len

    // int len;
    size_t len;
    
  8. Стиль: нет необходимости объявлять d так скоро. То же самое для c. Предложить изменение типа unsigned char c = va_arg(arg_list, int);. То же самое для str_arg.

    // int num;
    //  ... ~30 lines
    //    case 'd':
    //      num = va_arg(arg_list, int);
    
        case 'd':
            int num = va_arg(arg_list, int);
    
  9. Незначительный: упрощение кода

    // if (uppercase) {
    //     c = INT_TO_STR_DIGITS_U[r];
    // ...
    
    if (uppercase) {
        c = "0123456789ABCDEF"[r];
    } else {
        c = "0123456789abcdef"[r];
    }
    
  10. Угловая ошибка: ниже кода сбой (UB). Следите за size == 0

    my_snprintf(str, 0, fmt, ...)
    
  11. [Изменить] Ошибка: int uppercase = 0, base = 10; инициализируется вне for(). Таким образом, "%d" после "%x" будет обрабатывается как гексагон. A "%h" после использования "%H" как печать с прописными буквами. Простое исправление, переместите int uppercase = 0, base = 10; до else if (fmt[i] == '%') , Лучше исправить: передать базу и верхнюю /нижнюю как параметры в новый int_to_str().

  12. char *start_str не используется. Рекомендовать удаление.

  13. Переменные /функции, такие как INT_TO_STR_DIGITS_L[] и int_to_str(), которые предназначены только для локального использования, должны быть static. Непонятно, почему все прописные. Избегайте длинной строки, которая превышает ширину представления

    // int INT_TO_STR_DIGITS_L[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
    

    static int int_to_str_digits_l[16] = "0123456789abcdef";

ответил chux 25 J0000006Europe/Moscow 2016, 05:53:57

Похожие вопросы

0
Ванильная библиотека JavaScript для набора текста псевдокода в HTML-документах. Генерация потока Thepard Tone в модуле вычислений ClojureTimeoutable. MNIST. Глубокая нейронная сеть в TensorFlowIs. Эта быстрая реализация для изменения строки. Программа для поиска наибольшего нечетного числа среди трех переменных. Перенос робота в базу данных SQLite3. Подбор Scrabble WordC ++. Enigma MachineModern Vector реализация «Hello, world!» Программа, использующая класс для печати. ​​Это хороший метод isNaN? Печать большего символа в JavaRemove всех гласных из строки, кроме начального символаFormat RGB Long как шестнадцатеричная строка в VB6Converting строка в Integer в CProcessing XYZ-данные из большой строки fileCheck имеют уникальные символы только для проверки PALindrome Checker в JavaChecking для Null перед добавлением в CommonGridest общего divisorOptimizing Project Euler # 12 (Python 3) Sieve of Sundaram для Project Euler 7: реализация Python медленнее, чем C ++ и RZero-инициализация больших динамически распределенных массивов doubleMat математических функций на фракциях. Нужно ли еще проверять переднюю камеру?
39

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132