Segmented far pointer allocation in 16bit x86 MS-DOS real mode
Asked Answered
H

1

9

I'm trying to get my head around programming real mode MS-DOS in C. Using some old books on game programming as a starting point. The source code in the book is written for Microsoft C, but I'm trying to get it to compile under OpenWatcom v2. I've run into a problem early on, when trying to access a pointer to the start of VGA video memory.

#include <stdio.h>
#include <dos.h>

void Set_Video_Mode(int mode) {
    
    union REGS inregs, outregs;

    inregs.h.ah = 0; 
    inregs.h.al = (unsigned char) mode;

    int86(0x10, &inregs, &outregs);
}


int main(void)
{
    Set_Video_Mode(0x13);

    //the following line throws an error, without it the code compiles and runs
    char far *video_buffer = (char far *)0xA0000000L;

    while (!kbhit()) { };

    Set_Video_Mode(0x03);

    return 0;
} 

It's the far pointer assignment that throws the following errors:

VGA.C(33): Error! E1077: Missing '}'
VGA.C(33): Warning! W107: Missing return value for function 'main'
VGA.C(36): Error! E1099: Statement must be inside function. Probable cause: missing {

Which is kind of baffling, and seems like a macro definition gone wrong, or something...

When I try the code from the Wikipedia article on far pointers, with the same compiler:

#include <stdio.h>
int main() {
    char far *p = (char far *)0x55550005;
    char far *q = (char far *)0x53332225;
    *p = 80;
    (*p)++;
    printf("%d", *q);
    return 0;
}

It compiles. The compile command is wcl -bcl=dos source.c in both cases.

So I'm kind of stumped now, and can't seem to pinpoint the problem. I'm on the verge of throwing a few asterisks and brackets at random places to see if it sticks somewhere...

Hargeisa answered 2/4, 2022 at 21:40 Comment(14)
I see two differences between the sample you say does compile and one that does not: A) You included dos.h in the problem example, maybe there is something in that file that break compilation? B) Your use of L at the end of the pointer value. Maybe this particular compiler does not know it?Exsanguine
Note that the semicolon at the end of your while line is superfluous.Impearl
Can't you cross-compile with GCC?Marocain
@Marocain GCC doesn't have proper support for 16-bit segmented code or DOS. There is a ia16-gcc port of GCC but it is still unstable. That being said the ia16-gcc port is an interesting project but not ready for prime time.Aright
It's not dos.h. it's the standard DOS header needed for bios functions for video mode access in this case. I tried with it included in the second example without any effect. Likewise for the 'L' long literal. @Adrian Mole - good catch. See, I already started throwing random brackets around. Sigh.Hargeisa
@MichaelPetch: Ok... what about Borland C then? I remember that used to run on DOS.Marocain
@Marocain - the thing about OpenWatcom is its ability to cross-compile from modern OS-es. Although I'm not doing it in this case. Compiling with DOS version in dosbox-x. In any case Watcom is quite a standard later-era DOS compiler, probably one of the best and most famous. It's not it that's wrong, it's me, I just don't know how or where...Hargeisa
Try replacing the assignment with char far *video_buffer = NULL; to check which part of the assignment causes a problem.Frasco
@MichaelPetch - I just abbreviated that for clarity in the post. In actual code the function is implemented before main().Hargeisa
I don't remember well as a far pointer was encoded in the compiler form, but the x86 segmented processors were limited to 20bits of addressing, with the segment register value shifted by 4 and added to the 16 bits address to form the 20 bits physical address. Maybe the address 0xA0000000 is too large and has the MSB set. The VGA text memory starts at 0xB8000 as far as I know, which is in the 20 bits addressing range. All the address used in the examples you posted are outside of the addressing range for a 16bits segmented processor.Parahydrogen
@Parahydrogen : far pointers do exist in Watcom and they are encoded as 32 bit value with the segment in the upper 16 bit and the offset in the lower 16-bits. There is also a macro MK_FP that can make a far pointer out of a segment and offset value. 0xA0000000 would be interpreted by the Watcom DOS compiler as 0xa000:0x0000 which is the EGA/VGA graphics area (64KiB). Mode 0x13 (the OP is apparently doing that) would be using that area as video ram.Aright
@MichaelPetch Yes I remember now, MK_FP, defined in dos.h, so the physical address for the far pointer 0xA0000000 is 0xA0000 graphic memory of VGA.Parahydrogen
@MichaelPetch - it indeed does compile when I comment out those lines! As it does compile when I comment out the far pointer line... but together it breaks. I edited the original post to include the Set_Vide_Mode function.Hargeisa
And this is why reducing to a minimal reproducible example is useful for debugging.Fluxmeter
A
12

It appears your OpenWatcom C compiler is defaulting to using C89. In C89 variable declarations must be at the beginning of a block scope. In your case all your code and data is at function scope, so the variable has to be declared at the beginning of main before the code.

Moving the variable declaration this way should be C89 compatible:

int main(void)
{
    char far *video_buffer = (char far *)0xA0000000L;

    Set_Video_Mode(0x13);
    while (!kbhit()) { };
    Set_Video_Mode(0x03);

    return 0;
} 

If using OpenWatcom 2.0 as you suggest you are, you should be able to compile with C99 mode by adding the option -za99 to the wcl options. In C99 you can place variable declarations in places other than top of block scope.


When compiling as C89, Watcom extended C89 to allow C++ style comments the same way C99 supports it. This behaviour seem to be documented as:

The Open Watcom C/16 and C/32 compilers support an extension for comments. The symbol // can be used at any point in a physical source line (except inside a character constant or string literal). Any characters from the // to the end of the line are treated as comment characters. The comment is terminated by the end of the line.

I agree with the assessment that had the C++ style comments not been allowed, the compiler would have given a much better picture of what the real problem was. I too was fooled at first and it didn't occur to me right away that it was being compiled as C89 code. I had assumed since the // was accepted that it must have been C99.

Aright answered 2/4, 2022 at 23:40 Comment(4)
nitpick: C89 allows declarations at the top of any scope, not just whole-function scope. e.g. { int foo; ... }. godbolt.org/z/TzrKhc4WY shows GCC and clang allow that with -std=c89 -Wall -pedantic, but warn without the extra {} for a new scope, which I think is accurate to ISO C89.Fluxmeter
Yes, that's it! It's kind of weird that it still does accept line comments in c89 mode - that would be a dead giveaway if it didn't. I guess stuff like that was added as soon as it was available back then, before formal standardization.Hargeisa
@PeterCordes : Thanks I fixed it. My original wording was directed to the sample and it should have been a general statement.Aright
@Hargeisa : In fact the C++ style comments in your code deceived me as well. Since it never complained about them I had assumed it defaulted to C99 so it caused me head scratching. I forgot that Watcom added an extension for comments to allow // in C89.Aright

© 2022 - 2024 — McMap. All rights reserved.