VOGONS


First post, by keenmaster486

User metadata
Rank l33t
Rank
l33t

Title says it all. I have a class with some virtual functions that I override in another class that extends the base class. This strategy works as expected when compiled for 16 bit real mode DOS, but crashes the program when one of those functions is called in 32 bit protected mode.

Basically it looks like this:

class baseClass {
public:
virtual void functionThatGetsOverridden() {
// do something
}
}

class extendedClass: public baseClass {
public:
void functionThatGetsOverridden() {
baseClass::functionThatGetsOverridden();
// do something else here
}
}

There are other virtual functions that do get called and don't crash the program, but seem to be behaving rather oddly nonetheless. Hard to explain, just weird undefined behavior that shouldn't be happening. On the whole it just acts bizarrely in protected mode, while in real mode it behaves entirely as expected.

Yes, I've checked my data types, I'm explicitly using "short" when it matters.

Any suggestions?

World's foremost 486 enjoyer.

Reply 1 of 19, by jmarsh

User metadata
Rank Oldbie
Rank
Oldbie

You can't just call the same code in both modes, opcode encoding is based on the target mode...

Reply 2 of 19, by keenmaster486

User metadata
Rank l33t
Rank
l33t

Not sure what you mean. This is based on compiling the entire program either with wcl for real mode or wcl386 for protected mode.

World's foremost 486 enjoyer.

Reply 3 of 19, by pan069

User metadata
Rank Oldbie
Rank
Oldbie

It's been a very long time since I did any C++. Just to better understand why the virtual method? If you're simply looking for polymorphism then you don't need to define the method as virtual. If my memory serves me right, virtual methods are typically used in dynamic polymorphism, e.g. loading a driver, hot-swapping behavior at runtime.

Reply 4 of 19, by middlenibble

User metadata
Rank Newbie
Rank
Newbie

What compiler and linker options are you using?

Reply 6 of 19, by Falcosoft

User metadata
Rank l33t
Rank
l33t
keenmaster486 wrote on 2026-04-23, 16:24:
Title says it all. I have a class with some virtual functions that I override in another class that extends the base class. This […]
Show full quote

Title says it all. I have a class with some virtual functions that I override in another class that extends the base class. This strategy works as expected when compiled for 16 bit real mode DOS, but crashes the program when one of those functions is called in 32 bit protected mode.

Basically it looks like this:

class baseClass {
public:
virtual void functionThatGetsOverridden() {
// do something
}
}

class extendedClass: public baseClass {
public:
void functionThatGetsOverridden() {
baseClass::functionThatGetsOverridden();
// do something else here
}
}

There are other virtual functions that do get called and don't crash the program, but seem to be behaving rather oddly nonetheless. Hard to explain, just weird undefined behavior that shouldn't be happening. On the whole it just acts bizarrely in protected mode, while in real mode it behaves entirely as expected.

Yes, I've checked my data types, I'm explicitly using "short" when it matters.

Any suggestions?

Just forget this OOP mental masturbation 😀
In the DOS era when performance really mattered no one used such constructs because of the slowdown caused by vtables/indirections.

Website, Youtube
Falcosoft Soundfont Midi Player + Munt VSTi + BassMidi VSTi
VST Midi Driver Midi Mapper
x86 microarchitecture benchmark (MandelX)

Reply 7 of 19, by st31276a

User metadata
Rank Member
Rank
Member
pan069 wrote on 2026-04-24, 02:56:

It's been a very long time since I did any C++. Just to better understand why the virtual method? If you're simply looking for polymorphism then you don't need to define the method as virtual. If my memory serves me right, virtual methods are typically used in dynamic polymorphism, e.g. loading a driver, hot-swapping behavior at runtime.

Just overriding the function would be like:
extendedClass a;
a.functionThatGetsOverridden();

Polymorphism is:
baseClass *b = new extendedClass;
b->functionThatGetsOverridden();

Latter must be declared virtual to work, and builds a dynamic virtual function table at run time in which every new object's correct virtual function addresses are tracked so that the correct ones can be called, because they cannot be inferred from the derived class type at that point.

It's basically so that you can stuff a whole gaggle of different derived class type objects into the same container of base class type and have all the derived class objects act according to their own type instead of the type of the base class.

OW mustnt bugger that up.

Reply 8 of 19, by pan069

User metadata
Rank Oldbie
Rank
Oldbie

main.cpp:

#include <iostream>
using namespace std;

// Base class
class Animal {
public:
void animalSound() {
cout << "The animal makes a sound \n";
}
};

// Derived class
class Pig : public Animal {
public:
void animalSound() {
cout << "The pig says: wee wee \n";
}
};

// Derived class
class Dog : public Animal {
public:
void animalSound() {
cout << "The dog says: bow wow \n";
}
};

int main() {
Animal myAnimal;
Pig myPig;
Dog myDog;
myAnimal.animalSound();
myPig.animalSound();
myDog.animalSound();
return 0;
}

compile:

export LIB=$WATCOM/lib386:$WATCOM/lib386/dos
wcl386 -i=$WATCOM/h -l=dos4g main.cpp

output:

Open Watcom C/C++ x86 32-bit Compile and Link Utility
Version 2.0 beta Jan 26 2020 00:36:10 (32-bit)
Copyright (c) 2002-2020 The Open Watcom Contributors. All Rights Reserved.
Portions Copyright (c) 1988-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.
See http://www.openwatcom.org/ for details.
wpp386 main.cpp -i=/usr/bin/watcom/h
Open Watcom C++ x86 32-bit Optimizing Compiler
Version 2.0 beta Jan 26 2020 00:30:34 (32-bit)
Copyright (c) 2002-2020 The Open Watcom Contributors. All Rights Reserved.
Portions Copyright (c) 1989-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.
See http://www.openwatcom.org/ for details.
main.cpp: 131 lines, included 1755, no warnings, no errors
Code size: 60
wlink @__wcl__.lnk
Open Watcom Linker Version 2.0 beta Jan 26 2020 00:24:39 (32-bit)
Copyright (c) 2002-2020 The Open Watcom Contributors. All Rights Reserved.
Portions Copyright (c) 1985-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.
See http://www.openwatcom.org/ for details.
loading object files
searching libraries
creating a DOS/4G executable

execute:

DOS/4GW Protected Mode Run-time  Version 1.97
Copyright (c) Rational Systems, Inc. 1990-1994
The animal makes a sound
The pig says: wee wee
The dog says: bow bow

Reply 9 of 19, by st31276a

User metadata
Rank Member
Rank
Member

That's not polymorphism, that's just overloading the name / re-implementing the function in the derived class.

Polymorphism would be:

int main() {
Animal *myAnimal = new Animal;
Animal *myPig new Pig;
Animal *myDog new Dog;
myAnimal->animalSound();
myPig->animalSound();
myDog->animalSound();
return 0;
}

All animals would just "make a sound" instead of wee weeing and bow bowing if not declared virtual in base class.

Polymorphism means "more than one form" and is all about the base class - it is the base class type that has many forms in the form of different derived class object types looking like the base class type type wise, but calling their own derived class functions (only those marked virtual in base class)

(...and you might want to delete those pointers too... 😀

Reply 10 of 19, by wbahnassi

User metadata
Rank Oldbie
Rank
Oldbie

keenmaster486, would be good to attach the debugger and see the crash site.. I barely used OpenWatcom, but indeed it shouldn't mess up with C++ basics like that.
You can put a breakpoint just before the virtual function call, and trace the steps to see what the generated instructions are doing for grabbing the vtable and calling the function at the right offset.
It might turn out to be bad code-gen on the compiler side.
It is also worth trying different compiler switches to see if anything makes a difference..optimization settings, memory model options... etc.

Turbo XT 12MHz, EGA, MFM HDD
Intel 386 DX-33, Speedstar 24X, SB 1.5, 1x CD
Intel 486 DX2-66, CL5428 VLB, SBPro 2, 2x CD
IBM BlueLightning 100MHz, CL5428, SB16, 4x CD
Intel Pentium 90, Matrox Millenium 2, SB16, 4x CD
HP Z400, Xeon 3.46GHz, YMF-744, RTX2060

Reply 11 of 19, by llm

User metadata
Rank Member
Rank
Member

using latest Watcom V2 release "Version 2.0 beta Apr 24 2026": https://github.com/open-watcom/open-watcom-v2 … 0-c-win-x64.exe

main.cpp

#include <iostream>

class Animal {
public:
virtual void animalSound() { std::cout << "The animal makes a sound \n"; }
};

class Pig: public Animal {
public:
void animalSound() { std::cout << "The pig says: wee wee \n"; }
};

class Dog: public Animal {
public:
void animalSound() { std::cout << "The dog says: bow wow \n"; }
};

int main() {
Animal* a = new Animal();
Animal* b = new Pig();
Animal* c = new Dog();

a->animalSound();
b->animalSound();
c->animalSound();

delete a;
delete b;
delete c;

return 0;
}

build.bat

set TOOLS=c:\temp\tools

set WATCOM=%TOOLS%\open-watcom-2_0-c-win-x64
set WATCOM_BIN=%WATCOM%\binnt64
set INCLUDE=%WATCOM%\h
set PATH=%WATCOM_BIN%;%PATH%

del main.obj main.exe

wcl386 -bt=dos -l=dos4g main.cpp

gives this output and creates a main.exe

Open Watcom C/C++ x86 32-bit Compile and Link Utility
Version 2.0 beta Apr 24 2026 04:37:12 (64-bit)
Copyright (c) 2002-2026 The Open Watcom Contributors. All Rights Reserved.
Portions Copyright (c) 1988-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.
See https://github.com/open-watcom/open-watcom-v2#readme for details.
wpp386 main.cpp -bt=dos
Open Watcom C++ x86 32-bit Optimizing Compiler
Version 2.0 beta Apr 24 2026 04:31:17 (64-bit)
Copyright (c) 2002-2026 The Open Watcom Contributors. All Rights Reserved.
Portions Copyright (c) 1989-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.
See https://github.com/open-watcom/open-watcom-v2#readme for details.
main.cpp: 133 lines, included 1753, no warnings, no errors
wlink @__wcl_00.lnk
Open Watcom Linker Version 2.0 beta Apr 24 2026 04:20:51 (64-bit)
Copyright (c) 2002-2026 The Open Watcom Contributors. All Rights Reserved.
Portions Copyright (c) 1985-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.
See https://github.com/open-watcom/open-watcom-v2#readme for details.
loading object files
searching libraries
creating a DOS/4G executable

copying c:\temp\tools\open-watcom-2_0-c-win-x64\binw\dos4gw.exe into the main.exe folder
and running it with latest freshly-built dosbox-svn gives

DOS/4GW Protected Mode Run-time  Version 1.97
Copyright (c) Rational Systems, Inc. 1990-1994
The animal makes a sound
The pig says: wee wee
The dog says: bow wow

seems to work

using DOS/32A from: https://github.com/amindlost/dos32a also works

running

DOS32A.EXE main.exe

gives this output

DOS/32A -- DOS Extender version 9.1.2
Copyright (C) 1996-2006 by Narech K.
The animal makes a sound
The pig says: wee wee
The dog says: bow wow

so you need to provide(with recent compiler version tested) a minimal failing example, because your real example seems to be different in little details
an failing example is at least needed for filing a report on github

Reply 12 of 19, by keenmaster486

User metadata
Rank l33t
Rank
l33t

Thanks for the help so far guys - note that I mentioned some virtual functions work fine and others don't.

Given that you haven't revealed some quirk about OW C++ in 32 bit vs 16 bit that I wasn't aware of, I'm assuming the following: there is a pointer somewhere that I'm clobbering some memory with, that it can get away with in 16 bit real mode but not in 32 bit protected mode.

I'm trying to find it but having some trouble.

It would be difficult to post a minimal failing example without sending you the source of my entire project. I'll keep working on it and let you guys know if I find anything.

World's foremost 486 enjoyer.

Reply 13 of 19, by keenmaster486

User metadata
Rank l33t
Rank
l33t

Okay... I tried using DOS/32A instead of DOS/4GW, and the program works fine.

Interesting. Not going to assume the extender was the problem, but this might make it easier to debug.

World's foremost 486 enjoyer.

Reply 14 of 19, by llm

User metadata
Rank Member
Rank
Member

its still not clear what Watcom variant you're using 😀

I'm assuming the following: there is a pointer somewhere that I'm clobbering some memory with, that it can get away with in 16 bit real mode but not in 32 bit protected mode.

got some more tips for you - they sound complicated but easier to reach as some think

try to make your program compile (objects only) and maybe build(link)/run-able(depends on how DOS/hardware related your program is) native on linux or windows using recent compilers and tools to check for the error

1. compile

use some #ifdef __clang__ , __GNUC__ or _MSC_VER to create some stub functions or missing defines (near,far,outb etc.) - just make it compile-able (not linkable)

then you can use -Wall -Wpedantic -Wextra with gcc/clang, clang-tidy with clang and -fanalyzer with gcc or highest warning level with MSVC
that can give you many hints that good old Watcom is just not able to give

i did that with my DOS Stunts game MT32 driver reversing project - reverse the sound driver and reimplement it in C for DOS
https://github.com/LowLevelMahn/UnifiedMT15
https://github.com/LowLevelMahn/UnifiedMT15/b … /clang_test.cmd
https://github.com/LowLevelMahn/UnifiedMT15/blob/main/drv.c <-- some #ifdef __clang__ to make it compileable

that is something i would always try because compiling is way less complex then linking and running it

2. build and make it runnable

it depends very much on how much DOS or hardware is used in your code - the goal is to get as much code as running as possible, maybe by stubbing out sound-output or graphics-output etc.
or port it trivially over to use SDL/Allegro or something - keep the code as unchanged as possible so it can be still work on DOS

then you can use valgrind or the even better address-sanitizer (ASAN) with gcc/clang (which will find your problem instantly)

gcc or clang -O -g -fsanitize=address -fno-omit-frame-pointer main.cpp
the linker needs also the -fsanitize=address flag

also working with MSYS2 (https://www.msys2.org/) on windows, a linux distro using Windows/WSL2 or using direct Microsoft MSVC asan support

or using clang/clang-tidy directly from windows (what i've done for my UnifiedMT15 project - but without asan feature at that time):
https://github.com/llvm/llvm-project/releases … /llvmorg-22.1.0 (release Feb 24, 2026)
https://github.com/llvm/llvm-project/releases … 2.1.0-win64.exe

try to use recent tool versions - a gcc 8 or clang 5 tool set is less capable as recent ones, using a Tumbleweed, Fedora or Arch linux with up to date tools in a VM or a Dockerimage is nearly a no brainer 😀

i can guide you through this if interested

Reply 15 of 19, by llm

User metadata
Rank Member
Rank
Member

just try to port/compile and maybe link your cpp on windows with a recent LLVM/Clang https://github.com/llvm/llvm-project/releases use LLVM-22.1.4-win64.exe
for installation - tested with recent LLVM 22.1.4

@echo off

rem to find clang.exe, lld linker
set PATH=C:\Program Files\LLVM\bin;%PATH%
rem to find ASAN runtime "clang_rt.asan_dynamic-x86_64.dll"
set PATH=C:\Program Files\LLVM\lib\clang\22\lib\windows;%PATH%

del main.obj
del main.exe

rem example 1 -------------------------
rem build exe directly
clang.exe main.cpp -O0 -g -fsanitize=address -fno-omit-frame-pointer -fuse-ld=lld -Wall -Wextra -Wpedantic -o main.exe

rem example 2 -------------------------
rem compile only
clang.exe -c main.cpp -O0 -g -fsanitize=address -fno-omit-frame-pointer -Wall -Wextra -Wpedantic -o main.obj
rem link obj
clang.exe main.obj -fsanitize=address -fuse-ld=lld -o main.exe

main.exe

rem do all tidy checks (not all are relevant and can be skipped by using -[check-name] or * somewere in the name to disable a group of checks
clang-tidy main.cpp -checks=*
rem leave some out (modernizer, warnings that are not relevant for Watcom C++ compilation)
rem clang-tidy main.cpp -checks=*,-cppcoreguidelines-*,-hicpp-*,-modernize-*,-misc-use-*,-llvmlibc-*,-google-*,-altera-*,-readability-* --

Reply 16 of 19, by wbahnassi

User metadata
Rank Oldbie
Rank
Oldbie

That's a good way to try locate the issue. I rarely use asan myself, but I also rarely hit memory stomp issues in my code bases. Personally I would hook the debugger and try to make sense what the problem is. Does the call stack suddenly jump to a strange location? That would tell that my object's base pointer is bad, and then I would trace why it's bad (e.g. potentially uninitialized?). If I suspect it's a mem stomp I put a data breakpoint on it and wait for the beast to fall into the trap 🙂

Turbo XT 12MHz, EGA, MFM HDD
Intel 386 DX-33, Speedstar 24X, SB 1.5, 1x CD
Intel 486 DX2-66, CL5428 VLB, SBPro 2, 2x CD
IBM BlueLightning 100MHz, CL5428, SB16, 4x CD
Intel Pentium 90, Matrox Millenium 2, SB16, 4x CD
HP Z400, Xeon 3.46GHz, YMF-744, RTX2060

Reply 17 of 19, by llm

User metadata
Rank Member
Rank
Member

That's a good way to try locate the issue.

especially with DOS stuff i try to be multi-platform and multi-compiler (even multi-assembler) buildable from the start - projects are normaly not that super huge and cross-building isn't such a burden today as it was in the 1990s

so having the latest compilers and their diagnostic tools + ASAN/TSAN/and the other SANs is something that is normaly just available for me when needed - but maybe only because im used to it in my daily business

my daily job C/C++ programs are just too big (Mio LOC) and too many developers (>10-20+) involved to not rely on such tools/diganostic features, codebases tend to grow faster then poeple can keep up with quality 😀

Personally I would hook the debugger and try to make sense what the problem is.

definitly also a way to go

Reply 18 of 19, by keenmaster486

User metadata
Rank l33t
Rank
l33t
llm wrote on 2026-04-26, 05:56:

its still not clear what Watcom variant you're using 😀

Sorry, yes, I'm using OW v2, whatever the latest build was from their GitHub some months ago. I can try the latest and see if it makes a difference.

llm wrote on 2026-04-26, 05:56:

try to make your program compile (objects only) and maybe build(link)/run-able(depends on how DOS/hardware related your program is) native on linux or windows using recent compilers and tools to check for the error

Good idea, and fortunately I thought ahead because I wanted to make it cross platform to begin with, and already made everything DOS-specific or that talks directly to hardware abstracted through an set of interface functions. I've already started in on building SDL versions of those functions. So far it compiles and runs with no video output in Windows 98, and exits cleanly, but that doesn't tell me much.

Thanks for all the other tips, guys. I will delve into them if I continue to be stumped.

World's foremost 486 enjoyer.

Reply 19 of 19, by llm

User metadata
Rank Member
Rank
Member

So far it compiles and runs with no video output in Windows 98, and exits cleanly, but that doesn't tell me much.

old compilers aren't as good with warnings for undefined behavior or something as new ones are - what compiler you're using for the Win98 build?

nothing beats ASAN in runtime, and clang-tidy comes with nice data-flow analysis