在 c 中迭代字符串 returns 为空(os 开发)

Iterating over string returns empty in c (os development)

我正在做一个 os,或者至少是在尝试做,但我偶然发现了一个问题。在尝试遍历字符串以转换为 char 以打印到屏幕时,返回的 char 似乎是空的!(我实际上是 os 开发的新手);这是代码片段:

int offset = 0;

void clear_screen() {
    unsigned char * video = 0xB8000;
    for(int i = 0; i < 2000; i+=2){
        video[i] = ' ';
    }
}

void printc(char c) {
    unsigned char * video = 0xB8000;
    video[offset] = c;
    video[offset+1] = 0x03;
    offset += 2;
}

void print(unsigned char *string) {
    char * sus = '[=12=]';
    uint32 i = 0;
    printc('|');
    sus[0] = 'a';
    printc(sus[0]);  //this prints "a" correctly
    string[i] = 'c';
    while (string[i] != '[=12=]') {
        printc(string[i]);   //this while loop is only called once 
        i++;                 //it prints " " only once and exits
    }
    printc('|');
}

int bootup(void)
{
    clear_screen();

    // printc('h');
    // printc('e');
    // printc('l');                     /* These work */
    // printc('l');
    // printc('o');

    print("hello"); //this doesn't

    return 1;

}

它打印的输出:

|a |

提前致谢!!

编辑

新打印功能

void print(unsigned char *string) {
    uint32 i = 0;
    printc('|');
    while (string[i] != '[=14=]') {
        printc('i');  //not printed
        printc(string[i]);
        i++;
    }
    printc('|');
}

还是不行

编辑 2 根据@lundin 的建议更新了代码

int offset = 0;

void clear_screen() {
    unsigned char * video = (unsigned char *)0xB8000;
    for(int i = 0; i < 2000; i+=2){
        video[i] = ' ';
    }
}

void printc(char c) {
    unsigned char * video = (unsigned char *)0xB8000;
    video[offset] = c;
    video[offset+1] = 0x03;
    offset += 2;
}

void print(const char *string) {
    int i = 0;
    printc('|');
    while (string[i] != '[=15=]') {
        printc('i');
        printc(string[i]);
        i++;
    }
    printc('|');
}

int bootup(void)
{
    clear_screen();
    // printc('h');
    // printc('e');
    // printc('l');
    // printc('l');
    // printc('o');
    print("hello");
    return 1;

}

堆栈:

init_lm:
    mov ax, 0x10
    mov fs, ax          ;other segments are ignored
    mov gs, ax

    mov rbp, 0x90000    ;set up stack
    mov rsp, rbp

    ;Load kernel from disk
    xor ebx, ebx        ;upper 2 bytes above bh in ebx is for cylinder = 0x0
    mov bl, 0x2         ;read from 2nd sectors
    mov bh, 0x0         ;head
    mov ch, 1           ;read 1 sector
    mov rdi, KERNEL_ADDRESS
    call ata_chs_read


    jmp KERNEL_ADDRESS

    jmp $

您的程序具有未定义的行为,因为它包含多行无效的 C。您将收到有关这些行的编译器消息。

  • unsigned char * video = 0xB8000; 等不是有效的 C,您需要显式转换。
  • 同样,char * sus = '[=11=]'; 也是无效的 C。您正试图将指针分配给单个字符,这没有意义。字符串处理初学者常见问题解答:Common string handling pitfalls in C programming。它还涉及内存分配基础知识。
  • sus[0] = 'a'; 等等,因为 sus 没有指向有效内存,所以你有非常未定义的行为。
  • 如果您实际上是在尝试访问物理内存地址,这不是正确的方法。您需要 volatile 限定指针。请参阅 How to access a hardware register from firmware?(在您的情况下,它可能不是寄存器,但 link 中的所有内容仍然适用 - 如何使用十六进制常量等)
  • 编辑:void print(unsigned char *string) ... string[i] = 'c'; 也是错误的。首先,您传递的 char* 不一定与 unsigned char* 兼容。那么你不应该从一个名为 print 的函数中修改传递的字符串,那是没有意义的。这应该是 const char* string 以防止此类错误。就目前而言,您正在将字符串文字传递给此函数,然后尝试修改它 - 这是未定义的行为,因为字符串文字是 read-only.

假设使用 gcc 或 clang,如果您希望阻止编译器使用无效的 C 代码生成可执行文件,请查看 What compiler options are recommended for beginners learning C? 在您的情况下,您可能还需要那里提到的 -ffreestanding 选项.

    char * sus = '[=10=]';

没有检查更多...但这会为 sus 分配一个空指针,而且很可能不是您想要的。

在继续之前,我建议阅读 OSDev wiki's page on text-based UIs

虽然这可能在某种程度上超出了问题的范围,但我强烈建议您不要手动将 character/attribute 值用作 unsigned char,您可能希望声明一个 struct 为这些对输入:

struct TextCell {
    volatile unsigned char ch;
    volatile uint8_t attribute;
};

(实际上,您可以通过对属性的各个前景、背景和装饰组件使用位域来对其进行更精细的处理,但这可能有点超前了。)

从那里您可以将文本缓冲区定义为常量指针:

const struct TextCell* text_buffer = (TextCell *)0xB8000;

你可以进一步定义

const uint16_t MAXH = 80, MAXV = 25;
uint16_t currv = 0, currh = 0;
struct TextCell* text_cursor = text_buffer;


void advance_cursor() {
    text_cursor++;
    if (currh < MAXH) {
        currh++;
    }
    else {
        currh = 0;
        if (currv < MAXV) {
            currv++;
        }
        else {
            /* handle scrolling */
        }
    }
}

void gotoxy(uint16_t x, uint16_t y) {
    uint16_t new_pos = x * y;
    if (new_pos > (MAXV * MAXH)) {
        text_cursor = text_buffer + (MAXV * MAXH);
        currh = MAXH;
        currv = MAXV;
    }
    else {
        text_cursor += new_pos;
        currh = x;
        currv = y;
}

这将导致对您的代码进行以下修改:

void kprintc(char c, uint8_t attrib) {
    text_cursor->ch = c;
    text_cursor->attribute = attrib;
    advance_cursor();
}

void kprint(const char *string, uint8_t attribs) {
    int i;
    for (i = 0; string[i] != '[=13=]'; i++) {
        kprintc(string[i], attribs);
    }
}

void clear_screen() {
    for(int i = 0; i < (MAXH * MAXV); i++) {
        kprintc(' ', 0);
    }
}

int bootup(void) {
    clear_screen();
    // kprintc('h', 0x03);
    // kprintc('e', 0x03);
    // kprintc('l', 0x03);
    // kprintc('l', 0x03);
    // kprintc('o', 0x03);
    kprint("hello", 0x03);
    return 1;
}

那么,为什么我要建议所有这些额外的东西?因为以这种方式进行调试要容易得多,主要是 - 它可以更好地划分关注点,并更有效地构建数据(或在本例中为视频文本缓冲区)。此外,您最终需要在项目的某个时刻执行类似的操作,因此如果它对现在有帮助,您最好现在就执行。

如果我在这方面不合时宜,请告诉我。