VOGONS


First post, by megatron-uk

User metadata
Rank Oldbie
Rank
Oldbie

I'm using Openwatcom to write some code for real-mode DOS, so it's a 16bit target, using the 'large' memory model (the -ml flag to the Watcom compiler).

I have an integer, addr, defined as:

long int addr;

According to page 20 of the Openwatcom C Language Reference manual, that corresponds to -2147483648 to 2147483648. That does not look to be ambiguous in any way.

However I do

addr = (256 * 256);
printf("%d\n", addr);

... and the value returned is 0. This appears as if addr is instead being defined as a unsigned int (in the 16bit Openwatcom variant), or as unsigned short int.

This is part of a larger function which takes an x/y screen coordinate and turns it in to the starting offset into the off-screen video buffer. The full function is:

int gfx_GetXYaddr(unsigned short int x, unsigned short int y){
// Turn a screen x/y coordinate into an offset into a vram buffer

long int addr;

addr = VRAM_START;
addr += (GFX_ROW_SIZE * y);
addr += (x * GFX_PIXEL_SIZE);

if ((VRAM_START + addr) > VRAM_END){
if (GFX_VERBOSE){
printf("%s.%d\t XY coords beyond VRAM buffer end address\n", __FILE__, __LINE__);
}
return -1;
}

return addr;
}

In the current implementation, VRAM_START is always 0, GFX_ROW_SIZE is defined as 640, and GFX_PIXEL_SIZE is 1. Unless my coordinates are in the upper left of the screen, all my calculations are coming out overflowing the unsigned int, or unsigned short int type (which I'm not using, as you can see above).

What I can't understand is why the long int addr is overflowing, when it should be well within its storage bounds.

The version of Openwatcom in use is:

Open Watcom C x86 16-bit Optimizing Compiler
Version 2.0 beta Jan 13 2021 00:25:21 (64-bit)
Copyright (c) 2002-2021 The Open Watcom Contributors. All Rights Reserved.
Portions Copyright (c) 1984-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.
See http://www.openwatcom.org/ for details.

My collection database and technical wiki:
https://www.target-earth.net

Reply 1 of 9, by mr.cat

User metadata
Rank Member
Rank
Member

If you're expecting long, shouldn't you be using %ld in printf() instead of %d?
You could also try adding cast in there, to prevent any unwanted type conversion taking place.

Reply 2 of 9, by megatron-uk

User metadata
Rank Oldbie
Rank
Oldbie

Yes, that was throwing me off the scent... the problem turns out to the caller of gfx_GetXYaddr(), it was doing:

int status;
status = gfx_GetXYaddr(100, 220); // As an example

I had a check in there to always make sure the function hadn't returned <0 (as in the case of a coordinate outside the vram buffer)... and you can immediately see that the storage class of the returned value is smaller than that needed to contain the number of bytes offset into the buffer. Thus unless the coords where in the top left, the value was nearly always overflowing, wrapping around to negative and (correctly!) causing my error handling code to have a fit and say "whoah! You can't plot a bitmap/font/line outside the visible video buffer area!".

I guess I need to make a further in-depth check of this code to make sure I've got the right storage sizes - it was all lifted from the previous implementation, but on DJGPP and GCC for the Sharp and NEC int is always 32bits....

My collection database and technical wiki:
https://www.target-earth.net

Reply 3 of 9, by megatron-uk

User metadata
Rank Oldbie
Rank
Oldbie

There's some definite weirdness with integer type conversions and casts with Open Watcom that I've never come across before.

Take the following example (where all the window_* variables have been previously declared as long int; -2147483648 to 2147483648). This is a small section of my code where I'm calculating the size of a screen fragment that can fit in each of the 64K memory windows of the current screen mode (640x400 for now, but left as a dynamic calculation in case I use different resolutions in the future):

window_segment = vesamodeinfo->WinASegment;
window_bytes = vesamodeinfo->WinSize * 1024;
window_x_max = (window_bytes - 1) % GFX_COLS;
window_y_max = (window_bytes - 1) / GFX_COLS;
windows_in_use = (GFX_COLS * GFX_ROWS) * (GFX_BPP / 8);

printf("%s.%d\t VESA memory window segment address: %xh\n", __FILE__, __LINE__, window_segment);
printf("%s.%d\t VESA memory window size: %ld bytes (at %d bytes/pixel)\n", __FILE__, __LINE__, window_bytes, GFX_PIXEL_SIZE);
printf("%s.%d\t VESA memory window columns: %ld\n", __FILE__, __LINE__, window_x_max);
printf("%s.%d\t VESA memory window rows: %ld\n", __FILE__, __LINE__, window_y_max);
printf("%s.%d\t VESA memory windows needed: %ld\n", __FILE__, __LINE__, windows_in_use);

That produces the following output:

src/gfx.c.124 VESA memory window segment address: a000h
src/gfx.c.125 VESA memory window size: 0 bytes (at 1 bytes/pixel)
src/gfx.c.126 VESA memory window columns: -1
src/gfx.c.127 VESA memory window rows: 0
src/gfx.c.128 VESA memory windows needed: -6144

That just looks crazy to me.

If I add an explicit type cast to long int in the window_bytes calculation as follows:

window_bytes = (long int) vesamodeinfo->WinSize * 1024;

Then I instead get the following output:

src/gfx.c.124 VESA memory window segment address: a000h
src/gfx.c.125 VESA memory window size: 65536 bytes (at 1 bytes/pixel)
src/gfx.c.126 VESA memory window columns: 255
src/gfx.c.127 VESA memory window rows: 102
src/gfx.c.128 VESA memory windows needed: -6144

I've not used a C compiler before where the individual operands of an arithmetic operation needed explicitly casting to the larger type of the destination variable. That just seems wacky to me.

It's almost as if at runtime the storage size of the window_* variables is being redefined as short int, which is the type of vesamodeinfo->WinSize. This seems a different behaviour than what I've come to expect on something ubiquitous like gcc. e.g. I wouldn't expect gcc to truncate the value of an int if I was calculating the product of two shorts. I'd expect overflow if I was storing it in another short, but not an int.

My collection database and technical wiki:
https://www.target-earth.net

Reply 4 of 9, by megatron-uk

User metadata
Rank Oldbie
Rank
Oldbie

Yeah, this is utterly crazy:

#DEFINE GFX_COLS 640
#DEFINE GFX_ROWS 400
long int windows_in_use;

windows_in_use = (GFX_COLS * GFX_ROWS);

That gives a result of -6144, because it has overflowed.

Explicitly cast both of those defined values:

windows_in_use = ((long int) GFX_COLS * (long int) GFX_ROWS);

That gives the correct result of 256000.

Can anyone tell me why I have to explicitly cast those two defined values?

My collection database and technical wiki:
https://www.target-earth.net

Reply 5 of 9, by jmarsh

User metadata
Rank Oldbie
Rank
Oldbie

C performs implicit promotion to int, that's why you can usually do things like adding two shorts and assign the result to int without caring about overflow. But your (16-bit) ints aren't big enough so you need explicit promotion.

(Literal values are typically interpreted as ints unless postfixed with l,ll,etc.)

Reply 6 of 9, by megatron-uk

User metadata
Rank Oldbie
Rank
Oldbie

Ok, so I'll need to be careful wherever I've used these (or other) literal values throughout the code - thank you so much for the clarification.

Most of my C knowledge comes from a unix background (started back in the 90's on Sun OS, then IRIX, various BSD's and Linux), so the vagaries of some of the restrictions on machines with a 16bit word size are new to me.

My collection database and technical wiki:
https://www.target-earth.net

Reply 7 of 9, by mkarcher

User metadata
Rank l33t
Rank
l33t
megatron-uk wrote on 2021-01-31, 11:19:

Most of my C knowledge comes from a unix background (started back in the 90's on Sun OS, then IRIX, various BSD's and Linux), so the vagaries of some of the restrictions on machines with a 16bit word size are new to me.

It's exactly the same rules you have on 32 bit systems if you want to do 64-bit arithmetic.

     long long int foo = 65536*65536;
long long int foo2 = 65536LL * 65536;

foo will have the value zero on any typical 32-bit unix system (because the calculation is done using 32-bit arithmetic and overflows), even if it supports 64-bit long or long long variables. foo2 will have the expected value.

Or the same rules you get with integers and floating point:

    float two = 5 / 2;
float two_and_a_half = 5.0 / 2;

The first calculation is done using integers, the second one with floating point arithmethics. You just need to get used to those rules already kicking in if you exceed 16 bits, not only if you exceed 32 bits.

Reply 8 of 9, by Azarien

User metadata
Rank Oldbie
Rank
Oldbie
megatron-uk wrote on 2021-01-31, 10:58:
Yeah, this is utterly crazy: […]
Show full quote

Yeah, this is utterly crazy:

#DEFINE GFX_COLS 640
#DEFINE GFX_ROWS 400
long int windows_in_use;

windows_in_use = (GFX_COLS * GFX_ROWS);

That gives a result of -6144, because it has overflowed.

In my understanding (but I may be wrong) both 640 and 400 are of type `int`. The result of int * int is also int.
However, because your ints are only 16-bit, you get overflow. Signed overflow in C is undefined behavior, so the exact value is irrelevant.

Define your constants as long, and you'll get correct result.

#define GFX_COLS 640L
#define GFX_ROWS 400L

Reply 9 of 9, by BloodyCactus

User metadata
Rank Oldbie
Rank
Oldbie

so openwatcom has several things going on here.

one you should use inttypes.h for printing so like printf("%"PRIu32"\n", var); (PRIu32, PRIu16, etc). check out inttypes.
two, never use int/long/short. use stdint.h and use portable types in16_t, uint16_t, int32_t, uint32_t etc.

if your using real mode, even with with 386 cpu (-3/-4/-5 etc), its promoting to 16bit, not 32bit unless your explicit in one of the source types

#include <stdio.h>
#include <inttypes.h>
#include <stdint.h>

void main(void)
{
uint32_t a;

// causes OW to do 16bit * 16bit, fixing result as 16bit.
a = 0xF000 + 0xF000;
printf("%"PRIu32"\n", a);

// causes OW to do proper promotion because it does not
// take lvalue type into consideration when calcuating result.
a = (uint32_t)0xF000 + 0xF000;
printf("%"PRIu32"\n", a);
}

wcl -3 test.c && test.exe
57344
122880

wcl386 -3 test.c && test.exe
122880
122880

--/\-[ Stu : Bloody Cactus :: [ https://bloodycactus.com :: http://kråketær.com ]-/\--