Va_args не принимает целое число со знаком в C

Я разрабатывал свое ядро ​​с помощью C. Создавая функцию kprintf (функция похожа на printf, но работает с ядром), я увидел, что целые числа со знаком (точно тип данных — long), va_args преобразует их в unsigned long.

Вот фрагмент кода: kPrint.c

#include <stdarg.h>

// skipped some lines not needed for this question
// ...

/******************
 * global variables needed for number to string conversion
 * ***************/
// store the converted string temporarily
static char convertedString[30];

// store the index of the filled portion
static char numberIndex;

// TODO: Create function like printf but which works here
_Bool kprintf(const char* stringFormat, ...) {
    char* strPtr = (char*)stringFormat;
    va_list arguements;
    va_start(arguements, stringFormat);

    while (*strPtr != 0) {

        switch (*strPtr)
        {
        case '\e':
            // printf() also ends the execution
            return 1;
        case '%':
            switch (*(strPtr + 1))
            {
            // signed decimal integer
            case 'd':
            case 'i':
                kprintf(IntToString(va_arg(arguements, long long))); // stringify the integer
                // *problem* here
                strPtr += 2;
                break;

            // unsigned decimal integer
            case 'u':
                kprintf(uIntToString(va_arg(arguements, uint64_t))); // stringify
                strPtr += 2;
                break;

            // will implement U & L case later
            // unsigned hexadecimal
            case 'x':
            case 'X':
                kprintf(uHexToString(va_arg(arguements, uint64_t))); // stringify
                // doesn't work now
                strPtr += 2;
                break;

            // will implement U & L case later
            // 6 numbers after decimal
            case 'f':
            case 'F':
                kprintf(DoubleToString(va_arg(arguements, double), 6)); // stringify
                // doesn't work now
                strPtr += 2;
                break;

            // 2 numbers after pt
            case 'g':
            case 'G':
                kprintf(DoubleToString(va_arg(arguements, double), 2));
                strPtr += 2;
                break;

            case 'c':
                kPrintChar((char)va_arg(arguements, int));
                //
                strPtr += 2;
                break;

            // another string
            case 's':
                kPrintfHelper(va_arg(arguements, char*)); // just to prevent recursion
                strPtr += 2;
                break;

            case '%':
                kPrintChar('%');
                strPtr += 2;
                break;
            }
            break;
        default:
            kPrintChar(*strPtr);
            strPtr++;
        }
    }
    va_end(arguements);
    return 0;
}

void uIntToStrHelper(uint64_t num) {
    if (num < 10)
    {
        convertedString[numberIndex++] = num + '0';
        convertedString[numberIndex] = 0;
        return;
    }

    uint8_t numIndexCpy = numberIndex;

    while (num > 0) {
        convertedString[numberIndex++] = (num % 10) + '0';
        num /= 10;
    }

    char swpIndex = (numberIndex - 2 + numIndexCpy) / 2;
    numberIndex = numberIndex - swpIndex - 1 + numIndexCpy;

    while (swpIndex >= numIndexCpy) {
        convertedString[swpIndex] = convertedString[swpIndex] + 
            convertedString[numberIndex];

        convertedString[numberIndex] = convertedString[swpIndex] -
            convertedString[numberIndex];

        convertedString[swpIndex] = convertedString[swpIndex] -
            convertedString[numberIndex];
        
        swpIndex--;
        numberIndex++;
    }

    convertedString[numberIndex] = 0;
}

char* uIntToString(uint64_t num) {
    numberIndex = 0;
    uIntToStrHelper(num);
    return convertedString;
}

char* IntToString(long long num) {
    numberIndex = 0;
    if (num < 0) {
        convertedString[numberIndex++] = '-';
        num = -num;
    }

    uIntToStrHelper(num);
    return convertedString;
}

Обновлено: Добавлены IntToString и uIntToString.

(Я не знаю, как это правильно сделать, но этого достаточно)

Обзор проблемы: падежи для «d» и «i» показывают место проблемы.
Прототип функции IntToString:

char* IntToString(long long num);
// part of the reason why I used long long as va_arg data type
// had also tried with long but with no luck :(

Я попробовал функции IntToString и uIntToString на компьютере с Linux, и они работают так, как задумано.

ПРИМЕЧАНИЕ: Я не тестировал kprintf(const char* stringFormat, ...) в контролируемой и легко отлаживаемой среде, как две другие функции, упомянутые чуть выше.

Другие функции, такие как DoubleToString и uHexToString, еще не тестировались, но в любом случае они не должны меняться / быть связаны с вопросом.

kPrintfHelper — это такая же функция, как и kprintf, но она не выполняет никаких проверок и просто печатает строку.

Моя среда
Компилятор: x86_64-elf-gcc
Флаги (также известные как CFLAGS): -ffreestanding -mno-red-zone -m64

Линкер: x86_64-elf-ld

Я использую qemu-system-x86_64 для запуска финального исполняемого файла.

Тесты

Я пробовал следующие случаи:

  1. Обычный беззнаковый случай
kprintf("Number: %d\n", 1234);

Выход:

Number: 1234

  1. Не работает, но вывод выглядит так, как будто va_args выполняет какую-то странную беззнаковую int математику (дополнительно доказано тестом 5, что это действительно было va_args, ИМО)
kprintf("Number: %d\n", -1234);

Выход:

Number: 4294966062

  1. Работает по назначению
kprintf("Number: %d\n", 1099511627775);

Выход:

Number: 1099511627775

  1. НО Это
kprintf("Number: %d\n", -1099511627775);

Выход:

Number: 1099511627775

  1. И здесь
kprintf("Number: %s\n", IntToString(-1234));

Выход:

Number: -1234

Это «как» не является точной копией printf + пока все просто.

0xdead 04.04.2022 13:46

Unseen IntToString() может быть дефектным для -1099511627775.

chux - Reinstate Monica 04.04.2022 13:47

Пожалуйста, покажите минимальный воспроизводимый пример особенно IntToString.

Jabberwocky 04.04.2022 13:50

Следует отметить, но я только что проверил kprintf("Number: %s\n", IntToString(-1099511627775));, и он отлично работает с выводом как Number: -1099511627775

0xdead 04.04.2022 13:52

Вызов kprintf("Number: %d\n", -1234); неверен, потому что %d извлекает long long. Должно быть kprintf("Число: %d\n", -1234LL);

Kaz 04.04.2022 13:55

@Kaz на самом деле добавил LL проблему, но почему преобразование int в long long должно изменить значение, разве это не должно быть похоже на неявное приведение типов? А почему это должно влиять только на -ve числа?

0xdead 04.04.2022 13:59

"А почему это должно влиять только на -ve числа?" является необоснованным, поскольку двух тестов недостаточно для подтверждения этого вывода. Попробуйте kprintf("Number: %d %d %d %d\n", 1234, 5678, 123, 456);

chux - Reinstate Monica 04.04.2022 14:07

Провели ряд тестов и пришли к выводу, что это влияет только на отрицательные 32-битные числа. Нужно проверить va_args документы в Интернете на предмет такого поведения и его причин. (Хотя причина уже упоминалась @dbush, но почему только числа -ve? Почему такая несправедливость?)

0xdead 04.04.2022 14:19

@idiot Кстати: printf("%lld", -1234); показывает ту же проблему, что и ваш код, по крайней мере, на моей платформе. %lld ожидает 64-битное целое число, но -1234 — это 32-битное целое число. Кстати, вы должны изменить свое имя пользователя. Этот вопрос слишком хорош для этого имени пользователя.

Jabberwocky 04.04.2022 14:21

Примечание: глобальная переменная numberIndex — это несколько плохой дизайн.

Jabberwocky 04.04.2022 14:25

В сторону: re «Почему эта несправедливость?» Попытка охарактеризовать поведение, связанное с va_args, как несправедливость кажется мелодраматичным, вам не кажется?

ryyker 04.04.2022 14:37

@Jabberwocky, вот, изменил мое имя пользователя на «0xdead»?. IK о том, что numberIndex является глобальным, он также используется в непроверенной функции DoubleToString, и я сосредоточусь на ее улучшении. До сих пор меня беспокоила только часть печати, которая решена, и сосредоточимся на дизайне после того, как она заработает должным образом.

0xdead 04.04.2022 14:39

@0xdead Аргументы Variadic в C не имеют безопасности типов. Определенные преобразования выполняются для определенных типов аргументов (например, значение short становится int или float становится double). После этого проверки типов или преобразования не происходит. Макрос va_arg понятия не имеет, какой объект был фактически передан; он слепо верит, что указанный тип правильный, и просто соответствующим образом интерпретирует биты из пространства аргументов.

Kaz 04.04.2022 17:05
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
13
58
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вызов kprintf("Number: %d\n", -1234); неверен, потому что %d извлекает long long. Должно быть kprintf("Number: %d\n", -1234LL);.

-1234 — 32-битный операнд. Проблема может заключаться в том, что это передается в 64-битном выровненном слове, но не расширяется до 64-битного знака.

Иными словами, значение -1234 в 64 битах должно быть fffffffffffffb2e, но 32-битный параметр создает в стеке изображение 00000000fffffb2e, то есть 4294966062.

Однако, согласно этой гипотезе, нам нужно пройти -1000, чтобы получить наблюдаемое число 429496629. Он не имеет никакого отношения к -1234. Может происходить что-то еще, например, биты мусора интерпретируются как данные.

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

Тип значения, которое вы передаете для своего спецификатора %d, не соответствует ожидаемому va_arg.

Вы говорите va_arg ожидать long long, но 1234 и -1234 имеют тип int. Эти типы имеют разный размер, поэтому va_arg извлекает из стека вызовов больше байтов, чем вы вложили.

Спецификаторы стандартного формата имеют модификаторы размера, поэтому они могут обрабатывать различные типы размеров. Вам нужно будет реализовать что-то подобное.

Другие вопросы по теме