Actually, that address is a holdover of realmode, not protected mode. While still mapped (up to a point) when you switch to protected mode, it does not need to, nor usually will, remain mapped to that address range. Furthermore, if you have enabled paging then that code could be doing all sorts of nasty things to the byte at 0xB8000. Additionally, your code only affects a single byte of memory, and thus doesn't actually print your string so much as it puts '!' to the byte at address 0xB8000.
Yes, sorry, I screwed up my code... The original version I wanted to post from one of my first working OS kernels is this:
char* screen = (char *) 0xB8000;
const char* str = "Hello, OS world!\0";
for( int index = 0; str[index] != '\0'; ++index)
screen[index] = str[index];
EDIT: I figured out why my code was getting screwed up... if you're using a variable named "i" as an array index the forums interprets it as italics lol! So now I fixed it so the code is correct.
However, when I tried to post it with the code/source tags it came out screwed up and was unreadable (for some reason the forums don't like array indexing [x] symbols in code/source tags)! So I tried to modify it to make it readable using just pointer arithmetic but it looks like I actually just [i]screwed it up (never incremented "screen" so, you're right) lol. I will fix the original example; thanks for pointing it out! Was very tired last night!
And yes, you're right... it is real-mode. Sorry for that mix-up too! But I was simply demonstrating how physical RAM can be manipulated in Ring-0 without causing a crash, and how to make a basic "Hello world" kernel work. You obviously wouldn't want to use this code in a real/practical OS project. It's just a contrived example to make a point. ;)