News:

Download Pelles C here: http://www.pellesc.se

Main Menu

Recent posts

#71
Bug reports / The digits beyond the fifteent...
Last post by KEL26 - September 12, 2026, 05:17:23 PM
Summary

On Pelles C 14.50 (x64), the conversion of a double to decimal text produces
digits that are correct to about fifteen significant figures. Digits
requested beyond that are produced, but they are wrong, not merely
imprecise.

The practical consequence is that the round-trip guarantee implied by
DECIMAL_DIG does not hold:

  -------------------------------- ------------------------------------------
  DECIMAL_DIG declared by                                                  17
  <float.h>                       

  DBL_DECIMAL_DIG declared by                                              17
  <float.h>                       

  finite normal doubles printed                   48,191 of 100,000 -- 48.19%
  with %.17g that do not read back
  as the same value               
  -------------------------------- ------------------------------------------

The same test program, built with another toolchain's runtime on the same
machine and run on the same values, reports zero failures.

The input side is not affected: strtod reads a correctly rounded
seventeen-digit decimal back to the exact bit pattern. Hexadecimal output,
%a, is not affected either. The fault appears to lie solely in the
binary-to-decimal conversion used by the printf family.

A single self-contained test program is attached. It needs no data files, no
arguments and no other libraries, and it runs in under a second.

Environment

  ------------------------------------- -------------------------------------
  Compiler                              Pelles ISO C Compiler, Version
                                        14.50.0, x64

  Switches                              -Tx64-coff -std:C17 -Ze -Zx

  Link                                  crt64.lib kernel32.lib, console
                                        subsystem

  Optimisation                          irrelevant: identical output
                                        unoptimised and at -Ot, -Os and -Ox

  FLT_EVAL_METHOD                       0
  ------------------------------------- -------------------------------------

The optimiser was eliminated first, by rebuilding at every optimisation
level and comparing the output byte for byte. All four builds agree with one
another and all four disagree with the correctly rounded digits, so this is
a runtime library matter and not a code generation one.

How it was found

The work in progress was unrelated: making a C++ image processing library
usable from Pelles C by compiling its C++ once into an object file and
presenting a pure C interface, so that a Pelles C program can call it
without a DLL. In that arrangement the C++ code is compiled by another
compiler, but every C function it calls -- malloc, memcpy, fopen, printf,
cos, sin -- is resolved at the final link by Pelles C's own crt64.lib. That
is the point of the design: one C runtime, one heap, one set of file
handles.

To check that the C interface altered nothing, thirteen image processing
computations were written twice: once in C, calling through the interface,
and once in C++, calling the library directly. The two sets of output images
were compared byte for byte. Twelve of the thirteen were identical. One
differed in a single pixel, by one grey level.

Chasing that one pixel is what turned this up. The chain of elimination was:

1.  The optimiser was ruled out, as described above.
2.  Both computations were instrumented to compare their floating point in
    memory, stage by stage, before anything was cast to a byte. All nine
    stages were bit-identical, so the interface was not altering anything.
3.  The small amount of arithmetic in the caller was compiled by both
    compilers from the same source and compared: 51,062 floats,
    bit-identical.
4.  What remained was the mathematical library. Because the design routes
    every C call to crt64.lib, the transform in question was using Pelles
    C's cos and sin where the C++ comparison used the other toolchain's.
    Measured over 4,096 angles: 2,061 of 8,192 values differ, by at most one
    unit in the last place. That is not a fault in anything -- neither
    library claims correctly rounded transcendentals -- and it fully
    explains one pixel in 65,536 landing on the boundary between grey 31 and
    grey 32.

The present report arises from step 4. While printing the cos values for
comparison, the two runtimes printed different decimal text for identical
bit patterns. That is not a question about mathematics; it is a question
about printf, and it turned out to be a real one.

The minimal test

decimal_dig_test.c, attached, does two things.

Part one takes three doubles as exact IEEE-754 bit patterns, so that no
decimal parsing happens before the test, and compares printf's output
against the correctly rounded digits. Those expected strings were obtained
independently, by arbitrary-precision arithmetic, and are written into the
program as literals.

Part two generates 100,000 finite normal doubles from a four-line xorshift
generator -- so the sample is identical on every machine and every run --
prints each with %.*g at DECIMAL_DIG, reads it back with strtod, and
compares the bits.

strtod is used as the reader because it was verified separately, against
exact bit patterns, to be correct on this implementation. If it were not,
part one would fail in the same direction and the two parts would disagree.

Results

Part one, on Pelles C 14.50

      bits 3feffff9606a6fbc
        %.17g  expected 0.99999684172825409      got 0.999996841728254        <-- DIFFERS
        %.20f  expected 0.99999684172825409334   got 0.99999684172825400310   <-- DIFFERS

      bits 3fefe9cdad01883a
        %.17g  expected 0.99729045667869021      got 0.99729045667869013      <-- DIFFERS
        %.20f  expected 0.99729045667869020697   got 0.99729045667869013413   <-- DIFFERS

      bits 400921fb54442d18
        %.17g  expected 3.1415926535897931       got 3.1415926535897932       <-- DIFFERS
        %.20f  expected 3.14159265358979311600   got 3.14159265358979318989   <-- DIFFERS

Two features are worth separating.

The digits beyond the fifteenth are wrong. In the first value the correct
digits are ...254093 34, and what is produced is ...254003 10. This is not a
rounding difference at the last digit; the sixteenth digit onwards is a
different number.

Fewer digits are produced than were requested. %.17g on the first value
returns 0.999996841728254, which is fifteen significant figures, not
seventeen. In the 100,000-value sample this happens in 9.84% of cases.

Part two, on Pelles C 14.50

      values tested                              100000
      failed to read back as the same double     48191  (48.19%)
      printed with fewer digits than requested   9836  (9.84%)

The same program, another runtime, same machine, same values

      values tested                              100000
      failed to read back as the same double     0  (0.00%)

How it varies with the precision requested

Over 4,000 finite normal doubles spanning the whole exponent range, counting
strings that differ from the correctly rounded conversion:

  conversion     Pelles C 14.50   control
  ------------ ---------------- ---------
  %.15g                   3.33%     0.00%
  %.16g                  29.93%     0.00%
  %.17g                  85.47%     0.00%
  %.18g                  97.75%     0.00%

And the round-trip failures over the same sample:

  conversion     Pelles C 14.50   control
  ------------ ---------------- ---------
  %.16g                  61.88%    44.77%
  %.17g                  48.10%     0.00%
  %.18g                  47.45%     0.00%

The %.16g row is expected to fail on both, since sixteen digits cannot name
every double; it is included as a control on the method. The %.17g row is
the one that matters, and the two columns should both be zero.

What is not affected

These were tested and are correct, which may help to localise the fault.

strtod is correct. Five exact seventeen-digit decimals were parsed and each
produced the expected bit pattern.

%a is correct. The hexadecimal form was checked across every binary exponent
from 2^-1070 to 2^1023, 2,094 values, and separately on DBL_MAX, DBL_MIN,
the largest subnormal and the smallest subnormal. All were exact. %a is
therefore a sound workaround for anyone who needs to write a double as text
without losing it.

The optimiser is not involved. Unoptimised, -Ot, -Os and -Ox all produce
identical output.

Why it matters

<float.h> on this implementation declares DECIMAL_DIG = 17 and
DBL_DECIMAL_DIG = 17. The C standard describes DECIMAL_DIG as the number of
decimal digits such that any floating value may be converted to decimal and
back with no change of value (C11, 5.2.4.2.2, paragraph 12). Seventeen is
the correct value for IEEE-754 double. The declaration is therefore right;
what is missing is the conversion that would make it true.

The consequence is quiet, which is what makes it serious. Any program that
writes double data as text -- a configuration file, a comma-separated
export, a log, a checkpoint to be resumed from, a scientific data file, a
coordinate in a header -- and that writes more than fifteen significant
figures in the belief that it is preserving the value, is altering the value
at the moment of writing, and nothing in the program will say so. The loss
is at the level of one or two units in the last place, which is exactly the
level at which it survives casual inspection and then accumulates.

A false lead, recorded so that nobody repeats it

An earlier version of the measuring program appeared to show %a producing a
grossly wrong result for DBL_MAX: 0x8.181818181818p-255 in place of
0x1.fffffffffffffp+1023. That was a fault in the measuring program, not in
Pelles C. The same program had earlier printed DBL_MAX with %.20f into a
128-byte buffer, and that conversion needs more than 330 characters. The
buffer overflow corrupted what followed.

It is recorded here because it was nearly reported as a defect, and because
the only reason it was not is that the result was checked a second time by a
different route.

How to reproduce

    pocc -Tx64-coff -std:C17 -Ze -Zx decimal_dig_test.c
    polink -subsystem:console -machine:x64 decimal_dig_test.obj ^
           crt64.lib kernel32.lib -out:decimal_dig_test.exe
    decimal_dig_test

The program returns 0 if both parts pass and 1 otherwise. For a comparison,
the identical source builds unchanged with any other C compiler.

Suggested area to look at

The pattern -- correct to about fifteen significant figures, then wrong
rather than absent, together with %g sometimes yielding fewer significant
digits than requested -- is consistent with the conversion being carried out
through an intermediate of limited precision, with the remaining digits
generated from a value that has already lost them, rather than by an exact
arbitrary-precision method such as Steele and White's Dragon4 or Grisu with
an exact fallback.

strtod on the same implementation is exact, so the necessary
arbitrary-precision machinery may already be present on the input side.



File attached  'decimal_dig_test.c':

/* ===========================================================================
 *  decimal_dig_test.c
 *  ---------------------------------------------------------------------------
 *  A self-contained test of one thing only: whether printf can write a double
 *  with DECIMAL_DIG significant digits such that strtod reads back the same
 *  value.
 *
 *  <float.h> declares
 *
 *      DECIMAL_DIG      the number of decimal digits needed to hold any
 *                       floating value of the widest supported type so that
 *                       converting it to decimal and back changes nothing
 *      DBL_DECIMAL_DIG  the same, for double
 *
 *  The C standard describes DECIMAL_DIG (C11 5.2.4.2.2, paragraph 12) as the
 *  number of digits for which that round trip is guaranteed.  This program
 *  simply takes the implementation at its word and checks.
 *
 *  IT NEEDS NOTHING.  No data files, no libraries beyond the standard ones,
 *  no command line arguments.  It runs in under a second.
 *
 *  HOW IT AVOIDS TESTING ITSELF
 *
 *      Part one sets its values from EXACT BIT PATTERNS, so no decimal
 *      parsing takes place before the test.  Each expected string was
 *      obtained independently, from arbitrary-precision arithmetic, and is
 *      written into this file as a literal.
 *
 *      Part two uses strtod to read the printed text back.  That is safe
 *      here: strtod was checked separately against exact bit patterns and is
 *      correct on this implementation.  If it were not, part one would fail
 *      in the same way and the two parts would disagree.
 *
 *  BUILD, Pelles C:
 *      pocc -Tx64-coff -std:C17 -Ze -Zx decimal_dig_test.c
 *      polink -subsystem:console -machine:x64 decimal_dig_test.obj \
 *             crt64.lib kernel32.lib -out:decimal_dig_test.exe
 *
 *  BUILD, any other C compiler, for comparison:
 *      cc -std=c11 -O1 decimal_dig_test.c -o decimal_dig_test
 * ======================================================================== */
#72
Bug reports / Re: Wrong code at every /O lev...
Last post by MrBcx - September 10, 2026, 11:42:46 PM
Kel26,

Yours is the most comprehensive, detailed, and coherent bug report that I've ever read.

It's also an excellent template for anyone wishing to share future bug reports.

Thanks for sharing ...
#73
Bug reports / Wrong code at every /O level: ...
Last post by KEL26 - September 10, 2026, 11:47:36 AM
Wrong code at every /O level: two masked zero-tests joined by && lose their masks


SUMMARY

When two masked zero-tests on the SAME variable are joined by &&, and either
mask is a run-time value rather than a compile-time constant, the generated
code discards both masks and tests the variable against itself.

This function

unsigned int decide(unsigned int x, unsigned int m)
{
    return !((x & m) != 0) && ((x & 0x100u) == 0);
}

is compiled as though it read  return x == 0;

No diagnostic is issued, at any warning level.


AFFECTED

  Compiler          Pelles ISO C Compiler, Version 14.50.0
  Targets           -Tx64-coff and -Tx86-coff, both affected
  Levels            /Os, /Ot, /Ox, /O1, /O2 -- every one
  Not affected      compiling with no /O switch
  Independent of    /Ze, /Zx, /Go, and the /std: setting
                    (verified across nine switch combinations,
                     and with /std:C99, /std:C11, /std:C17)


WHAT THE STANDARD REQUIRES

ISO/IEC 9899:2018 6.5.10p4 (&), 6.5.3.3p5 (!) and 6.5.13p3 (&&) give, for
x = 0x00000279 and m = 2:

x & m             = 0x279 & 0x002 = 0
(x & m) != 0      = 0
!((x & m) != 0)   = 1
x & 0x100u        = 0x279 & 0x100 = 0
(x & 0x100u) == 0 = 1
1 && 1            = 1

decide(0x279, 2) shall return 1.  It returns 0.

There is no undefined behaviour in the function.  Every operand is
unsigned int, so no signed overflow and no implementation-defined signed
conversion arises.  There is no shift, no pointer arithmetic, no aliasing and
no uninitialised read, and neither operand of && has a side effect.


GENERATED CODE

Compiled -Tx64-coff -std:C17 with no /O switch -- correct:

decide:
  mov   dword ptr [rsp+8],ecx
  mov   dword ptr [rsp+10],edx
  mov   eax,dword ptr [rsp+10]
  test  dword ptr [rsp+8],eax        ; x & m
  jne   L_false
  test  dword ptr [rsp+8],100        ; x & 0x100
  jne   L_false
  mov   eax,1
  ret
L_false:
  mov   eax,0
  ret

The same source, -Tx64-coff -Ot -std:C17 -- wrong:

decide:
  xor   eax,eax
  test  ecx,ecx                      ; x is tested against ITSELF
  sete  al
  ret

edx, which holds the parameter m, is never read.  The constant 0x100 has
disappeared as well.

The 32-bit target, -Tx86-coff /Ot, shows the same fault:

_decide:
  xor   eax,eax
  cmp   dword ptr [esp+4],0
  sete  al
  ret


WHAT APPEARS TO BE HAPPENING

Fusing two masked zero-tests on one variable,

    (x & m) == 0 && (x & n) == 0   ==>   (x & (m | n)) == 0

is a valid and worthwhile transformation, and it is performed CORRECTLY when
both masks are compile-time constants.  With m = 0x002 and n = 0x100 written
literally, the emitted code is exactly right:

both_const:
  xor   eax,eax
  test  ecx,102                      ; 0x002 | 0x100 = 0x102   CORRECT
  sete  al
  ret

The fault appears when either mask is not a constant.  Rather than computing
m | n at run time, or declining the transformation, the emitted test takes the
value register as its second operand.

A run-time mask can be built perfectly well when the fusion is written out by
hand, so the capability is clearly there:

unsigned int fused_by_hand(unsigned int x, unsigned int m)
{
    return ((x & m) | (x & 0x100u)) == 0;
}

fused_by_hand:
  xor   eax,eax
  or    edx,100                      ; m | 0x100
  test  ecx,edx
  sete  al
  ret                                ; CORRECT


WHICH FORMS ARE AFFECTED

All compiled -Tx64-coff -Ot -std:C17.

  form                                              emitted test    verdict
  ----------------------------------------------------------------------
  both masks compile-time constants                 test ecx,102    correct
  first mask a parameter, second constant           test ecx,ecx    WRONG
  first mask constant, second a parameter           test ecx,ecx    WRONG
  both masks parameters                             test ecx,ecx    WRONG
  three conjuncts, first mask a parameter           test ecx,ecx    WRONG
  signed int operands, otherwise identical          test ecx,ecx    WRONG
  shared value left in first &, right in second     test ecx,ecx    WRONG
  shared value is the right operand of both         two tests       correct
  shared operand is a variable mask, values differ  two tests       correct
  shared operand is a constant mask, values differ  two tests       correct
  both masks are enumeration constants              test ecx,14000  correct
  single masked test, variable mask, no &&          test ecx,edx    correct
  the two tests on different variables              two tests       correct
  fusion written by hand with |                     or, then test   correct
  || with != 0 (the De Morgan dual)                 two tests       correct
  ----------------------------------------------------------------------

Stated exactly: the fault arises when the LEFT operand of the & in an earlier
conjunct reappears, in either position, in a later conjunct's masked
zero-test, and at least one of the remaining operands is not a compile-time
constant.

Two further points, both measured rather than assumed:

  - An enumeration constant behaves exactly as a literal does.  Masks
    NO_UGLY = 0x10000 and CONSERVE_MEMORY = 0x4000, declared in an enum, fuse
    correctly to  test ecx,14000  -- byte for byte the code the literals give.

  - If the shared operand is the MASK rather than the value, as in
    (h & 3) == 0 && (w & 3) == 0 where two different values are tested against
    one constant, no fusion is attempted and the code is correct.

Signedness does not matter.  Hoisting the sub-expressions into ordinary
non-volatile locals does NOT avoid it.


CONTROL

The identical source compiled by MinGW-w64 GCC 13 at -O2 performs the same
fusion and gets it right:

decide:
  xor    %eax,%eax
  or     $0x1,%dh          # sets bit 8 of edx: m | 0x100
  test   %ecx,%edx
  sete   %al
  ret


WHERE THIS WAS FOUND IN REAL CODE

Building libtiff 4.7.2 at /Ot.  The function TIFFWriteEncodedStrip, in the
source file tif_write.c, contains

if (!isFillOrder(tif, td->td_fillorder) &&
    (tif->tif_flags & TIFF_NOBITREV) == 0)
    TIFFReverseBits((uint8_t *)data, cc);

with

#define isFillOrder(tif, o)  (((tif)->tif_flags & (o)) != 0)
#define TIFF_NOBITREV        0x00100U

At run time tif_flags = 0x00100279 and td_fillorder = 2, so the condition is
true and the bit reversal must happen.  The optimised build evaluated it as
false, skipped the reversal, and wrote every CCITT Group 3 fax image
bit-reversed while still tagging it FillOrder = 2.  The files opened
without complaint; the pixels were wrong.

Twelve sites in that library match this trigger.  No diagnostic was issued
for any of them.


TO REPRODUCE IN THIRTY SECONDS

The attached pellesc_1450_optimiser_bug.c is self-checking.  Compile it twice
and run both:

pocc   -Tx64-coff      -std:C17 -c pellesc_1450_optimiser_bug.c
polink -subsystem:console -machine:x64 pellesc_1450_optimiser_bug.obj ^
       crt64.lib kernel32.lib -out:noopt.exe
noopt.exe        ->  "All 7 cases correct."      exit status 0

pocc   -Tx64-coff -Ot  -std:C17 -c pellesc_1450_optimiser_bug.c
polink -subsystem:console -machine:x64 pellesc_1450_optimiser_bug.obj ^
       crt64.lib kernel32.lib -out:opt.exe
opt.exe          ->  "4 of 7 cases WRONG."       exit status 1


ATTACHED FILES

  src/pellesc_1450_optimiser_bug.c   self-checking reproducer, run it twice
  src/bug.c                          the single function, for disassembly
  src/variants.c                     ten of the forms in the table above
  src/characterise.c                 the same test across operand values
  src/hypothesis.c                   demonstrates the transformation performed
  src/shared_mask.c                  shared mask versus shared value
  src/position.c                     which operand position matters
  src/enum_masks.c                   enumeration constants as masks

  disassembly/bug_no_optimisation_x64.asm   correct
  disassembly/bug_Ot_x64.asm                WRONG
  disassembly/bug_Ot_x86.asm                WRONG
  disassembly/variants_Ot_x64.asm           all variants
  disassembly/shared_mask_Ot_x64.asm
  disassembly/position_Ot_x64.asm
  disassembly/enum_masks_Ot_x64.asm
  disassembly/bug_gcc_O2_control.asm        GCC 13 -O2, correct

All listings were produced with  podump /DISASM  on the object files built
from exactly the attached sources.
#74
Assembly discussions / Re: Find characters inside a s...
Last post by Vortex - September 09, 2026, 09:37:55 PM
Hi Timo,

Sorry for the late reply. I modified my posts above to present a better algo. Here are the comments :

OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE

FindChar PROC src:DWORD,char:DWORD

eax to point the string to search for a specific character :

    mov     eax,DWORD PTR [esp+4]

ecx holding the character to be found :

    mov     ecx,DWORD PTR [esp+8]

Decrement eax so modifying eax should not interfer with the only jmp jz

    dec     eax
@@:
    inc     eax

Get a byte from eax pointing the string

    mov     dl,BYTE PTR [eax]

Check if it's NULL terminator

    test    dl,dl

If dl is NULL set dh to 1

    setz    dh

If ( dl XOR cl ) == 0 the we found the char we were looking for.

dl XOR cl is zero if dl == cl :

    xor     dl,cl
    setz    ch

If none of the conditions above are met then return back to the top of the loop :   

    or      ch,dh
    jz      @b
    retn    8

FindChar ENDP

OPTION PROLOGUE:PrologueDef
OPTION EPILOGUE:EpilogueDef
#75
Add-ins / Add-Ins
Last post by TimoVJL - September 08, 2026, 11:39:08 AM
The poide.exe have an useful Add-In interface, so why not using it.

For example testing small code and see an assembly in Output window might be useful.
Also creating 32/64 bit projects easily.

Users could tell, what kind of tools they need to help their programming situations.

Hardly many IDE have same features.

EDIT 2026-09-16:
Testing debugger symbols in list view
https://forum.pellesc.de/index.php?msg=27982
#76
Bug reports / Re: optimiser emits ud2 (inval...
Last post by Michele - September 03, 2026, 08:38:21 AM
Quote from: KEL26 on September 03, 2026, 01:39:12 AM=====================================

Please, to all helpful users, read all of my text in each section,  before 'helping' me out - I explained the problem as clear as I could, with code and words - please explain using sentences rather than just pure code - the penny will then, hopefully, drop.
Kelly PellesC isn't open source, the only person that could take a corrective action is Pelle.
What the other users are doing is to supply other test cases for Pelle to hopefully help debugging.
Those aren't answers to help directly with your case.
We have to wait for Pelle now.

And I still couldn't understand the sense to insert such opcode, if the compiler meet a problem should fail under compilation phase, not let a time bomb instruction code in the executable, unless it was there to help compiler debug. And for some reason Pelle forget to remove it when the compiler was released.
#77
Bug reports / Re: optimiser emits ud2 (inval...
Last post by KEL26 - September 03, 2026, 01:39:12 AM
=====================================

Please, to all helpful users, read all of my text in each section,  before 'helping' me out - I explained the problem as clear as I could, with code and words - please explain using sentences rather than just pure code - the penny will then, hopefully, drop.
I am a professional programmer and I have been debugging C-language with (almost) 'ease' for years.  How to detect the bug, this code, has been shown in my zipped folder, that I attached for Dear Pelle and for all who wish to see, I appreciate explanations with any code you post to help me and others.

=====================================

Robert, thank you. Your printf line gives the clearest picture of the fault so far. Under /O2 it prints all two hundred stored values cycling 0 to 43, and it does so in both of your optimised runs. That is the bug in full: 300 modulo 256 is 44.

The change to the checksum line does not alter that, and your own output shows it. The printed array is identical, and wrong, in both /O2 runs. Only the checksum differs, and the reason is that

sum += (*data)++;

never reads the element at index i. It reads the first element two hundred times, adding the old value and incrementing it, so from zero it sums 0+1+...+199 = 19900 whatever the other 199 bytes contain. I checked this by filling the array with 0xFF in every byte but the first, which was set to zero: that loop still prints 19900.

So the fix changed the measurement rather than the data. The array your program prints is the real result. It is wrong under /O2 and right without it, which is exactly what Timo's element check found and what has now gone to Pelle. Your run makes it three machines.
#78
Bug reports / Re: optimiser emits ud2 (inval...
Last post by Robert - September 02, 2026, 10:54:39 PM
Quote from: TimoVJL on September 01, 2026, 07:42:46 PMint __cdecl printf(const char * restrict format, ...);
int __cdecl main(void)
{
    int i;
    unsigned long sum = 0;
    unsigned char *pdata;    // pointer to data
    unsigned char data[200];    // safer stack place at end ?

    pdata = data;
    for (i = 0; i < 200; i++)
        *pdata++ = (unsigned char)(i % 300);

    pdata = data;
    for (i = 0; i < 200; i++)
        sum += *pdata++;
        //sum += (*data)++;

    printf("checksum %lu (correct 19900)\n", sum);
    return 0;
}
also with
sum += (*data)++;output
checksum 19900 (correct 19900)
EDIT: a simple check
int __cdecl printf(const char * restrict format, ...);
int __cdecl main(void)
{
    unsigned char *pdata; // pointer to data
    int i;
    unsigned char data[200];

    pdata = data;
    for (i = 0; i < 200; i++)
    {
        *pdata = (unsigned char)(i % 300);
        if (*pdata != i)
        {
            printf("\nerror: %u != %u\n", i, *pdata);
            break;
        }
        printf("%u ", *pdata);
        pdata++;
    }
    return 0;
}


int __cdecl printf(const char * restrict format, ...);
int __cdecl main(void)
{
    int i;
    unsigned long sum = 0;
    unsigned char *pdata;    // pointer to data
    unsigned char data[200];    // safer stack place at end ?

    pdata = data;
    for (i = 0; i < 200; i++)
        *pdata++ = (unsigned char)(i % 300);

    pdata = data;
    for (i = 0; i < 200; i++)
        {
        printf("%i ", data[i]);
        // sum += *pdata++;
         sum += (*data)++;
        }

    printf("checksum %lu (correct 19900)\n", sum);
    return 0;
}

/*
/O2 optimization
using "sum += *pdata++;"
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 checksum 4060 (correct 19900)
*/

/*
/O2 optimization
using "sum += (*data)++;"
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 checksum 19900 (correct 19900)
*/
 
/*
No /O2 optimization
using "sum += *pdata++;"
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 checksum 19900 (correct 19900)
*/

/*
No /O2 optimization
using "sum += (*data)++;"
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 checksum 19900 (correct 19900)
*/

#79
Assembly discussions / NetUserGetInfo example
Last post by Vortex - September 02, 2026, 09:38:12 PM
Based on the NetUserGetInfo API function, command-line application displaying the lockout status of a local account :

include LockStatus.inc

.data

f1      dw 'The account %s is NOT locked.',0
f2      dw 'The account %s is locked.',0
f3      db 'Usage : LockStatus <Account>'
        db 13,10,0

sTable  dd OFFSET f1,OFFSET f2
ErrMsg  db 'NetUserGetInfo failed.',0

.data?

ui4     dd ? ; USER_INFO_4
args    dd ?

.code

start:

    call    main
    invoke  ExitProcess,eax
   
main PROC uses esi

    invoke  GetCommandLineW
    invoke  CommandLineToArgvW,eax,ADDR args

    cmp     args,1
    jne     @f

    invoke  printf,ADDR f3
    ret
@@:
    mov     esi,eax
    invoke  NetUserGetInfo,0,\
            DWORD PTR [eax+4],4,ADDR ui4

    test    eax,eax
    jz      @f

    invoke  printf,ADDR ErrMsg
    xor     eax,eax
    ret
@@:
    mov     eax,USER_INFO_4.usri4_flags[ui4]
    and     eax,UF_LOCKOUT
    shr     eax,2
    lea     edx,[sTable+eax]
    shr     eax,2
    push    eax
    invoke  wprintf,DWORD PTR [edx],\
            DWORD PTR [esi+4]

    invoke  NetApiBufferFree,ui4
    pop     eax
    ret

main ENDP

END start
#80
Bug reports / Re: optimiser emits ud2 (inval...
Last post by TimoVJL - September 02, 2026, 08:29:49 PM
Kelly,
You found a bug and now we have a small example code for it, so now just wait Pelle's opinion.
Using pointer was just an another test for it.

EDIT: for minimal executable for debugging, but needs msvcrt.libs :
#pragma comment(lib, "msvcrt.lib")
int __cdecl printf(const char * restrict format, ...);
int __stdcall ExitProcess(int);
void __cdecl mainCRTStartup(void)
{
    unsigned char *pdata; // pointer to data
    int i;
    unsigned char data[200];

    pdata = data;
    for (i = 0; i < 200; i++)
    {
        *pdata = (unsigned char)(i % 300);
        if (*pdata != i)
        {
            printf("\nerror: %u != %u\n", i, *pdata);
            break;
        }
        printf("%u ", *pdata);
        pdata++;
    }

    ExitProcess(0);
}

EDIT: useful Add-Ins ?

https://forum.pellesc.de/index.php?topic=5248.0

https://forum.pellesc.de/index.php?topic=4467.0

https://forum.pellesc.de/index.php?msg=26516