va_args 在 C 中不接受有符号整数

va_args not accepting signed integer in 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 arguments;
    va_start(arguments, 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(arguments, long long))); // stringify the integer
                // *problem* here
                strPtr += 2;
                break;

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

            // will implement U & L case later
            // unsigned hexadecimal
            case 'x':
            case 'X':
                kprintf(uHexToString(va_arg(arguments, 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(arguments, double), 6)); // stringify
                // doesn't work now
                strPtr += 2;
                break;

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

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

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

            case '%':
                kPrintChar('%');
                strPtr += 2;
                break;
            }
            break;
        default:
            kPrintChar(*strPtr);
            strPtr++;
        }
    }
    va_end(arguments);
    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;
}

编辑: 添加了 IntToStringuIntToString

(我不知道正确的做法,但足够了)

问题概述: '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 :(

我已经在 linux 机器上尝试了 IntToStringuIntToString 功能,它们按预期方式工作。

注意: 我没有像上面提到的其他 2 个函数那样在受控且易于调试的环境中测试 kprintf(const char* stringFormat, ...)

DoubleToStringuHexToString 等其他功能尚未测试,但无论如何都不应该 change/be 与问题相关。

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,IMO)
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

调用 kprintf("Number: %d\n", -1234); 不正确,因为 %d 提取了一个 long long。必须是 kprintf("Number: %d\n", -1234LL);.

-1234 是一个 32 位操作数。问题可能是这是在 64 位对齐字中传递的,但没有符号扩展到 64 位。

也就是说,64位的-1234值需要是fffffffffffffb2e,但是32位的参数在栈上产生了一个00000000fffffb2e的图像,就是4294966062。

然而,根据这个假设,我们必须传递 -1000 才能获得观察到的 429496629。它与 -1234 无关。可能发生了其他事情,例如垃圾位被解释为数据。

行为不是 well-defined,毕竟:您将一个大小的整数推入一个完全无类型且不安全的参数传递机制并拉出一个不同大小的整数。

您为 %d 说明符传递的值的类型与 va_arg 期望的不匹配。

您告诉 va_arg 期待 long long,但 1234-1234 的类型为 int。这些类型的大小不同,因此 va_arg 从调用堆栈中提取的字节数多于您放入的字节数。

标准格式说明符具有大小修饰符,因此它们可以处理不同的大小类型。您需要实现类似的东西。