Я разрабатывал свое ядро с помощью 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
для запуска финального исполняемого файла.
Я пробовал следующие случаи:
kprintf("Number: %d\n", 1234);
Выход:
Number: 1234
va_args
выполняет какую-то странную беззнаковую int
математику (дополнительно доказано тестом 5, что это действительно было va_args
, ИМО)kprintf("Number: %d\n", -1234);
Выход:
Number: 4294966062
kprintf("Number: %d\n", 1099511627775);
Выход:
Number: 1099511627775
kprintf("Number: %d\n", -1099511627775);
Выход:
Number: 1099511627775
kprintf("Number: %s\n", IntToString(-1234));
Выход:
Number: -1234
Unseen IntToString()
может быть дефектным для -1099511627775.
Пожалуйста, покажите минимальный воспроизводимый пример особенно IntToString
.
Следует отметить, но я только что проверил kprintf("Number: %s\n", IntToString(-1099511627775));
, и он отлично работает с выводом как Number: -1099511627775
Вызов kprintf("Number: %d\n", -1234);
неверен, потому что %d
извлекает long long
. Должно быть kprintf("Число: %d\n", -1234LL);
@Kaz на самом деле добавил LL
проблему, но почему преобразование int
в long long
должно изменить значение, разве это не должно быть похоже на неявное приведение типов? А почему это должно влиять только на -ve числа?
"А почему это должно влиять только на -ve числа?" является необоснованным, поскольку двух тестов недостаточно для подтверждения этого вывода. Попробуйте kprintf("Number: %d %d %d %d\n", 1234, 5678, 123, 456);
Провели ряд тестов и пришли к выводу, что это влияет только на отрицательные 32-битные числа. Нужно проверить va_args
документы в Интернете на предмет такого поведения и его причин. (Хотя причина уже упоминалась @dbush, но почему только числа -ve? Почему такая несправедливость?)
@idiot Кстати: printf("%lld", -1234);
показывает ту же проблему, что и ваш код, по крайней мере, на моей платформе. %lld
ожидает 64-битное целое число, но -1234 — это 32-битное целое число. Кстати, вы должны изменить свое имя пользователя. Этот вопрос слишком хорош для этого имени пользователя.
Примечание: глобальная переменная numberIndex
— это несколько плохой дизайн.
В сторону: re «Почему эта несправедливость?» Попытка охарактеризовать поведение, связанное с va_args
, как несправедливость кажется мелодраматичным, вам не кажется?
@Jabberwocky, вот, изменил мое имя пользователя на «0xdead»?. IK о том, что numberIndex
является глобальным, он также используется в непроверенной функции DoubleToString
, и я сосредоточусь на ее улучшении. До сих пор меня беспокоила только часть печати, которая решена, и сосредоточимся на дизайне после того, как она заработает должным образом.
@0xdead Аргументы Variadic в C не имеют безопасности типов. Определенные преобразования выполняются для определенных типов аргументов (например, значение short
становится int
или float
становится double
). После этого проверки типов или преобразования не происходит. Макрос va_arg
понятия не имеет, какой объект был фактически передан; он слепо верит, что указанный тип правильный, и просто соответствующим образом интерпретирует биты из пространства аргументов.
Вызов 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
извлекает из стека вызовов больше байтов, чем вы вложили.
Спецификаторы стандартного формата имеют модификаторы размера, поэтому они могут обрабатывать различные типы размеров. Вам нужно будет реализовать что-то подобное.
Это «как» не является точной копией
printf
+ пока все просто.