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:
1class baseClass { 2 public: 3 virtual void functionThatGetsOverridden() { 4 // do something 5 } 6} 7 8class extendedClass: public baseClass { 9 public: 10 void functionThatGetsOverridden() { 11 baseClass::functionThatGetsOverridden(); 12 // do something else here 13 } 14}
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.
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.
keenmaster486wrote 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:
1class baseClass { 2 public: 3 virtual void functionThatGetsOverridden() { 4 // do something 5 } 6} 7 8class extendedClass: public baseClass { 9 public: 10 void functionThatGetsOverridden() { 11 baseClass::functionThatGetsOverridden(); 12 // do something else here 13 } 14}
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.
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.
1#include <iostream> 2using namespace std; 3 4// Base class 5class Animal { 6 public: 7 void animalSound() { 8 cout << "The animal makes a sound \n"; 9 } 10}; 11 12// Derived class 13class Pig : public Animal { 14 public: 15 void animalSound() { 16 cout << "The pig says: wee wee \n"; 17 } 18}; 19 20// Derived class 21class Dog : public Animal { 22 public: 23 void animalSound() { 24 cout << "The dog says: bow wow \n"; 25 } 26}; 27 28int main() { 29 Animal myAnimal; 30 Pig myPig; 31 Dog myDog; 32 myAnimal.animalSound(); 33 myPig.animalSound(); 34 myDog.animalSound(); 35 return 0; 36}
compile:
1export LIB=$WATCOM/lib386:$WATCOM/lib386/dos
1wcl386 -i=$WATCOM/h -l=dos4g main.cpp
output:
1Open Watcom C/C++ x86 32-bit Compile and Link Utility 2Version 2.0 beta Jan 26 2020 00:36:10 (32-bit) 3Copyright (c) 2002-2020 The Open Watcom Contributors. All Rights Reserved. 4Portions Copyright (c) 1988-2002 Sybase, Inc. All Rights Reserved. 5Source code is available under the Sybase Open Watcom Public License. 6See http://www.openwatcom.org/ for details. 7 wpp386 main.cpp -i=/usr/bin/watcom/h 8Open Watcom C++ x86 32-bit Optimizing Compiler 9Version 2.0 beta Jan 26 2020 00:30:34 (32-bit) 10Copyright (c) 2002-2020 The Open Watcom Contributors. All Rights Reserved. 11Portions Copyright (c) 1989-2002 Sybase, Inc. All Rights Reserved. 12Source code is available under the Sybase Open Watcom Public License. 13See http://www.openwatcom.org/ for details. 14main.cpp: 131 lines, included 1755, no warnings, no errors 15Code size: 60 16 wlink @__wcl__.lnk 17Open Watcom Linker Version 2.0 beta Jan 26 2020 00:24:39 (32-bit) 18Copyright (c) 2002-2020 The Open Watcom Contributors. All Rights Reserved. 19Portions Copyright (c) 1985-2002 Sybase, Inc. All Rights Reserved. 20Source code is available under the Sybase Open Watcom Public License. 21See http://www.openwatcom.org/ for details. 22loading object files 23searching libraries 24creating a DOS/4G executable
execute:
1DOS/4GW Protected Mode Run-time Version 1.97 2Copyright (c) Rational Systems, Inc. 1990-1994 3The animal makes a sound 4The pig says: wee wee 5The dog says: bow bow
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... 😀
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
1Open Watcom C/C++ x86 32-bit Compile and Link Utility 2Version 2.0 beta Apr 24 2026 04:37:12 (64-bit) 3Copyright (c) 2002-2026 The Open Watcom Contributors. All Rights Reserved. 4Portions Copyright (c) 1988-2002 Sybase, Inc. All Rights Reserved. 5Source code is available under the Sybase Open Watcom Public License. 6See https://github.com/open-watcom/open-watcom-v2#readme for details. 7 wpp386 main.cpp -bt=dos 8Open Watcom C++ x86 32-bit Optimizing Compiler 9Version 2.0 beta Apr 24 2026 04:31:17 (64-bit) 10Copyright (c) 2002-2026 The Open Watcom Contributors. All Rights Reserved. 11Portions Copyright (c) 1989-2002 Sybase, Inc. All Rights Reserved. 12Source code is available under the Sybase Open Watcom Public License. 13See https://github.com/open-watcom/open-watcom-v2#readme for details. 14main.cpp: 133 lines, included 1753, no warnings, no errors 15 wlink @__wcl_00.lnk 16Open Watcom Linker Version 2.0 beta Apr 24 2026 04:20:51 (64-bit) 17Copyright (c) 2002-2026 The Open Watcom Contributors. All Rights Reserved. 18Portions Copyright (c) 1985-2002 Sybase, Inc. All Rights Reserved. 19Source code is available under the Sybase Open Watcom Public License. 20See https://github.com/open-watcom/open-watcom-v2#readme for details. 21loading object files 22searching libraries 23creating 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
1DOS/4GW Protected Mode Run-time Version 1.97 2Copyright (c) Rational Systems, Inc. 1990-1994 3The animal makes a sound 4The pig says: wee wee 5The dog says: bow wow
1DOS/32A -- DOS Extender version 9.1.2 2Copyright (C) 1996-2006 by Narech K. 3The animal makes a sound 4The pig says: wee wee 5The 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
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.
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
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)
1gcc or clang -O -g -fsanitize=address -fno-omit-frame-pointer main.cpp 2the 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
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 😀
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
1@echo off 2 3rem to find clang.exe, lld linker 4set PATH=C:\Program Files\LLVM\bin;%PATH% 5rem to find ASAN runtime "clang_rt.asan_dynamic-x86_64.dll" 6set PATH=C:\Program Files\LLVM\lib\clang\22\lib\windows;%PATH% 7 8del main.obj 9del main.exe 10 11rem example 1 ------------------------- 12rem build exe directly 13clang.exe main.cpp -O0 -g -fsanitize=address -fno-omit-frame-pointer -fuse-ld=lld -Wall -Wextra -Wpedantic -o main.exe 14 15rem example 2 ------------------------- 16rem compile only 17clang.exe -c main.cpp -O0 -g -fsanitize=address -fno-omit-frame-pointer -Wall -Wextra -Wpedantic -o main.obj 18rem link obj 19clang.exe main.obj -fsanitize=address -fuse-ld=lld -o main.exe 20 21main.exe 22 23rem 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 24clang-tidy main.cpp -checks=* 25rem leave some out (modernizer, warnings that are not relevant for Watcom C++ compilation) 26rem clang-tidy main.cpp -checks=*,-cppcoreguidelines-*,-hicpp-*,-modernize-*,-misc-use-*,-llvmlibc-*,-google-*,-altera-*,-readability-* --
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
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.
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.