在16位x86 MS-DOS实模式下分段远指针分配

9

我试图了解如何使用C语言编程实模式MS-DOS。以一些旧的游戏编程书籍为起点。
书中的源代码使用Microsoft C编写,但我正在尝试在OpenWatcom v2下编译它。当我尝试访问指向VGA视频内存起始位置的指针时,遇到了一个问题。

#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;
} 

以下是翻译的结果:

是远指针分配引发了以下错误:

VGA.C(33):错误!E1077:缺少 '}'
VGA.C(33):警告!W107:函数'main'缺少返回值
VGA.C(36):错误!E1099:语句必须在函数内。可能原因:缺少 {

这有点令人困惑,看起来像宏定义出错了,或者其他什么问题...

当我使用维基百科上的远指针文章中的代码,并使用同样的编译器时:

#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;
}

它已经编译了。 在这两种情况下,编译命令都是wcl -bcl=dos source.c

我有点困惑了,似乎无法确定问题所在。 我快要随意添加一些星号和括号,看看是否会起作用...


我发现您提到的能够编译的示例与不能编译的示例存在两个区别:A)您在问题示例中包含了“dos.h”,可能该文件中有一些内容会导致编译出错?B)您在指针值的末尾使用了“L”。也许这个特定的编译器不认识它? - Lev M.
请注意,while 行末的分号是多余的。 - Adrian Mole
这不是dos.h。在这种情况下,需要标准的DOS头文件以便进行BIOS功能的视频模式访问。我尝试过将其包含在第二个示例中,但没有任何效果。对于'L'长整型字面量也是如此。 @Adrian Mole - 很好的发现。你看,我已经开始随意添加括号了。唉。 - IronPug
1
@Frankie_C: far指针在Watcom中确实存在,它们被编码为32位值,其中段在上16位,偏移量在下16位。还有一个宏MK_FP,可以将段和偏移值制作成远指针。0xA0000000将被Watcom DOS编译器解释为0xa000:0x0000,这是EGA/VGA图形区域(64KiB)。模式0x13(OP显然正在使用该模式)将使用该区域作为视频RAM。 - Michael Petch
5
这就是为什么缩小到一个 [mcve] 对于调试很有用。 - Peter Cordes
显示剩余9条评论
1个回答

12

看起来你的OpenWatcom C编译器默认使用C89。 在C89中,变量声明必须在块作用域的开头。 在你的情况下,所有的代码和数据都是在函数作用域内,因此变量必须在main代码之前声明。

将变量声明移动到这个位置应该符合C89标准:

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

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

    return 0;
} 

如果您正在使用您提到的OpenWatcom 2.0版本,您可以通过将选项-za99添加到wcl选项中来编译C99方式。在C99中,您可以将变量声明放置在块作用域以外的其他位置。


当以C89方式编译时,Watcom将C89扩展以允许C++风格的注释,就像C99支持的一样。这种行为似乎被文档记录如下:

Open Watcom C/16和C/32编译器支持注释的扩展。符号//可以在物理源行的任何位置使用(除了字符常量或字符串文字内部)。从//到行尾的任何字符都被视为注释字符。注释由行末结束。

我同意评估结果是,如果不允许使用C++风格的注释,那么编译器会给出更好的错误信息。我也被欺骗了一段时间,一开始没有想到它是以C89代码编译的。我之所以这样假设,是因为//被接受了,我认为它必须是C99。


1
小细节: C89 允许在任何作用域的顶部进行声明,而不仅限于整个函数作用域。例如:{int foo; ... }。https://godbolt.org/z/TzrKhc4WY 显示 GCC 和 clang 允许使用 -std=c89 -Wall -pedantic 进行声明,但是在没有额外的 {} 的情况下对新作用域发出警告,我认为这符合 ISO C89 的要求。 - Peter Cordes
1
没错,就是这样!在c89模式下仍然接受行注释有点奇怪 - 如果不是这样的话,那就太明显了。我猜当时一些类似的东西在正式标准化之前就被添加进去了。 - IronPug
@PeterCordes:谢谢,我已经修复了。我的原始措辞是针对示例的,实际上应该是一般性的陈述。 - Michael Petch
1
@IronPug:实际上,你代码中的C++风格注释也误导了我。因为它从未抱怨过它们,所以我假设它默认为C99,这让我感到困惑。我忘记Watcom添加了一个扩展来允许在C89中使用//注释。 - Michael Petch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接