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