June 27, 2024, 08:35:45 PM

News:

Own IWBasic 2.x ? -----> Get your free upgrade to 3.x now.........


C Runtime Library Anomaly

Started by Logman, November 05, 2013, 06:53:44 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Logman

November 05, 2013, 06:53:44 PM Last Edit: November 05, 2013, 07:15:40 PM by Logman
Larry:

I've got a problem I can't seem to resolve related to _printf in the C Runtime crtdll library.

The following code using INT variables works perfectly:



OPENCONSOLE

INT iVar = 25
STRING sFormat = "C runtime result = %d"

_asm
extern _printf

 push dword [$iVar]
 push dword $sFormat
 call _printf
 add esp,8
_endasm

PRINT
PRINT "  IWBasic result = ", iVar
DO: UNTIL INKEY$ <> ""
CLOSECONSOLE
END



I used IWBasic variables for simplicity, but also built this program with .data/.text sections within the assembly code block with similar results.

However, when switching to FLOAT type variables, I get garbage out. Here's the code:



OPENCONSOLE

FLOAT fVar = 25.0
STRING sFormat = "C runtime result = %f"

_asm
extern _printf
 
 push dword [$fVar]
 push dword $sFormat
 call _printf
 add esp,8
_endasm

PRINT
PRINT "  IWBasic result = ", fVar
DO: UNTIL INKEY$ <> ""
CLOSECONSOLE
END



I'm not sure what's going on here. Any suggestions?

Logman
Education is what you get when you read the fine print.<br />Experience is what you get when you don't!

LarryMc

Don't have a clue.
This gives the error also:
DECLARE CDECL EXTERN _printf(format as STRING,...)
float fVar = 25.0
STRING sFormat = "\nC runtime result = %5.2f"
_printf(sFormat, fVar )

but this works fine
DECLARE CDECL EXTERN _printf(format as STRING,...)
double fVar = 25.0
STRING sFormat = "\nC runtime result = %5.2f"
_printf(sFormat, fVar )
LarryMc
Larry McCaughn :)
Author of IWB+, Custom Button Designer library, Custom Chart Designer library, Snippet Manager, IWGrid control library, LM_Image control library

Logman

Curious. I tried all kinds of combinations using just IWBasic code, just inline assembly code, combinations of both and couldn't get the _printf to work correctly with float type variables.

I also used various width and size specifiers as you did. I'll work on the problem some more today.

Logman
Education is what you get when you read the fine print.<br />Experience is what you get when you don't!

srvaldez

I seem to recall that Ibasic used to return a single float in register eax and not in the fpu register st(0), that could be the reason for single not working with printf.
please don't shoot me if my memory is wrong  ;D

Logman

SrValdez:

I'm looking at that too.

Using IDA Pro, I see that NASM pulls in __imp_printf and _printf as an integer routine/jump address for both integers and floats. Not sure what's going on here, but am contacting the folks over at the NASM project to see if they can let me know what I'm doing incorrectly if anything.

Logman
Education is what you get when you read the fine print.<br />Experience is what you get when you don't!

Logman

Quote from: LarryMc on November 05, 2013, 10:08:25 PM
Don't have a clue.
This gives the error also:
DECLARE CDECL EXTERN _printf(format as STRING,...)
float fVar = 25.0
STRING sFormat = "\nC runtime result = %5.2f"
_printf(sFormat, fVar )

but this works fine
DECLARE CDECL EXTERN _printf(format as STRING,...)
double fVar = 25.0
STRING sFormat = "\nC runtime result = %5.2f"
_printf(sFormat, fVar )


Actually, the output I get from _printf(sFormat, fVar) is 25.0026. I'm running IWBasic 2.0 on Windows 7 64-bit and it appears NASM is incorrectly translating instructions with the C runtime. The output should be: _25.00 (that is, 5 characters wide including the decimal) and it should be displaying only 2 digits after the decimal.

Logman
Education is what you get when you read the fine print.<br />Experience is what you get when you don't!

GWS

I've no idea what's going on ..  ::) - it seems like more headaches from the 'superpower' languages that you don't get if you use good old, dying out Basic  :P

Is it a mental exercise, or something you really need to use?

Just using IWB and it's formatted output, you get:


OPENCONSOLE

FLOAT fVar = 25.00

a$ = "%f####.##"
PRINT "  Format result  =",using(a$,fvar)
PRINT "  IWBasic result = ", fVar

DO: UNTIL INKEY$ <> ""
CLOSECONSOLE
END


.. both of which give 25.00.

In IWB you have to have the right type specifier (%d) if you use a Double.  %f results in 0.00  :o
That little problem doesn't arise in the much simpler CBasic ..   ;D

all the best, :)

Graham
Tomorrow may be too late ..

LarryMc

@graham
Logman is writing tutorials for coding in assembly language.  He is using IWBasic's ability to handle assembly code to demonstrate some of what  he is doing.

@Logman
DECLARE CDECL EXTERN _printf(format as STRING,...)
double fVar = 25.0
STRING sFormat = "\nC runtime result = %5.2f"
_printf(sFormat, fVar )

gives me _25.00 like what it is suppose to and not _25.0026 like you are saying above.

I'm using IWB 2.095 on a win7 x64 machine.
LarryMc
Larry McCaughn :)
Author of IWB+, Custom Button Designer library, Custom Chart Designer library, Snippet Manager, IWGrid control library, LM_Image control library

GWS

Oh .. that explains it ..  :)

Graham
Tomorrow may be too late ..

Logman

November 07, 2013, 11:23:36 AM #9 Last Edit: November 07, 2013, 11:55:44 AM by Logman
GWS:

I'm putting together a tutorial and a book for classroom use at a university on how to use inline assembly in BASIC programming code. This is a valuable IWBasic capability. It makes it much easier to instruct assembly language programming to students because IWBasic already includes many of the DLLs and runtime libraries that assembly students would otherwise have to create on their own. And, unlike some other popular BASICs, IWBasic uses a linker, which makes it far easier to bring in other pre-coded routines whether they be APIs, libraries, object files from other languages, or whatever.

Besides, I think it is important that all serious software development professionals should know how to add inline assembly code to their BASIC and C/C++ programs.

LarryMc, one other thing I tried was to change to $OPTION "float" since $OPTION "double" is the default. Still has me confounded.

It's not as if I'd ever use this code, it's just an exercise in tracking down something that shouldn't be happening the way it does without a good explanation. Students are sure to discover this anomaly and certainly would have questions. I note that even IWBasic changes 32-bit floats to 64-bit doubles before it calls the _sprint function to print out floating point results to the screen.

Since all the code resides within an assembly _asm/_endasm block, I'm presuming all the code gets passed to NASM as it generally does. So, I'm focusing my effort in figuring out why NASM is not translating the instruction correctly. I get the same results when I substitute nasmw in the IWBdev/bin folder with a version of yasm. My next test is to write a stand-alone NASM program to see what the differences may be in the code.

Here's a tip: I have replaced the NASM version of nasmw used by IWBasic with the most current version of NASM 2.11rc2. However, I still get the same results regarding floats--very curious.

I'm going to keep plugging away at it. I'm sure there's a very simple solution. It might end up being something simple in the crtdll.dll runtime library file located in the IWBdev/lib subdirectory.

GWS: Your last code example does work perfectly, but remember, IWBasic's PRINT command is a wrapper to _sprintf, not to _printf AFAIK.

Logman
Education is what you get when you read the fine print.<br />Experience is what you get when you don't!

Logman

November 08, 2013, 07:27:21 PM #10 Last Edit: November 08, 2013, 07:47:16 PM by Logman
LarryMc:

Okay, did some research and found out what was happening. Apparently, the C runtime floating point formatter %f takes 64-bit QWORD-sized arguments. I was under the assumption that it could use 32-bit DWORD floats. After some research, and I'm not a C expert, I determined that you can't pass it a FLOAT value. If you do, all you get for results is garbage.

One clue is that IWBasic's PRINT command sends floating point values to the _ibprintfloat subprocedure, which then translates any 32-bit floats to 64-bit doubles before sending the variable on to the _sprintf/_printf C runtime function to be formatted using the %f specifier.

The second clue came from you Larry. You converted the variable fVar from a FLOAT to a DOUBLE to get the _printf function to work. When I ran your code, it ran as expected and I also got 25.0 as a result. You were right.

Larry's code:


DECLARE CDECL EXTERN _printf(format as STRING,...)
double fVar = 25.0
STRING sFormat = "\nC runtime result = %5.2f"
_printf(sFormat, fVar )


So, for instance if you are going to use the %f formatter, you have to send it a 64-bit double (QWORD) floating point value. However, since IWBasic is a 32-bit compiler, you can't PUSH a 64-bit (QWORD) value onto the stack. The trick is to PUSH two 32-bit units. This is done by PUSHing the lower 32-bits then PUSHing the higher 32-bits separately. Now the program works perfectly.

Here's the new code. You can either store a variable value as a DOUBLE, or if you store it as a FLOAT, you have to convert it to a DOUBLE. I show this conversion using the FPU to make a teaching point. I could have just stored the variable as a DOUBLE.



OPENCONSOLE

FLOAT fVar = 25.0                              ; 32-bit floating point value
STRING sFormat = "C runtime result = %5.2f"    ; Width = 5 characters including period, use 2 decimal places
DOUBLE dVar                                    ; 64-bit floating point value

_asm
extern _printf
 
 finit                   ; Initialize FPU
 fld dword [$fVar]       ; Load DWORD (FLOAT type = 32-bit DWORD) into FPU ST0 register
 fstp qword [$dVar]      ; Save QWORD (DOUBLE = 64-bit QWORD) into dVar

 push dword [$dVar+4]    ; Push lower 32-bit DWORD
 push dword [$dVar]      ; Push Upper 32-bit DWORD
 push dword $sFormat     ; Push address of sFormat
 call _printf            ; Call C runtime function
 add esp,12              ; Remove 12 bytes off the stack
_endasm

PRINT
PRINT "  IWBasic result =", dVar
DO: UNTIL INKEY$ <> ""
CLOSECONSOLE
END




Floating point variables can be stored as: 25.0 or 1.2345e20.

Thanks for letting me bounce this off the forum. GWS and srvaldez, thanks for your input.

Logman
Education is what you get when you read the fine print.<br />Experience is what you get when you don't!

LarryMc

Thank you for sticking with it; figuring out what was going on; and for explaining it in an understandable manner.
LarryMc
Larry McCaughn :)
Author of IWB+, Custom Button Designer library, Custom Chart Designer library, Snippet Manager, IWGrid control library, LM_Image control library