News:

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

Main Menu

Silly test for msvcrt and ucrt sprintf

Started by TimoVJL, February 06, 2026, 11:28:10 AM

Previous topic - Next topic

TimoVJL

Just a silly test for msvcrt and ucrt sprintf.

pocrt: 3.141593
 452 ticks
msvcrt: 3.141593
 827 ticks
ucrt: 3.141593
 749 ticks

test_sprintf3pocrt:  3.141593
 439.653040
msvcrt: 3.141593
 795.308200
ucrt:   3.141593
 691.476080
May the source be with you

jack


jack

will give a good test Monday, I am off to visit my family and won't be back till Monday

jack

@TimoVJL I am back early
trying to link against ucrt with Pelles C is not straightforward but it easy using llvm-mingw gcc from https://github.com/mstorsjo/llvm-mingw/releases
using the 64-bit version I compiled the following code: gcc sprintf-timing.c -o sprintf-timing
sprintf-timing.c
#include <stdio.h>
#include <windows.h>

#define ITERATIONS 1000000

int main() {
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
    char buffer[256];
    LARGE_INTEGER frequency, start, end;
    double x=1.2345678901234567890;
    double total_time = 0;
   
    QueryPerformanceFrequency(&frequency);
   
    for (int i = 0; i < ITERATIONS; i++) {
        QueryPerformanceCounter(&start);
       
        sprintf(buffer, "%.15g", x);
       
        QueryPerformanceCounter(&end);
       
        total_time += (double)(end.QuadPart - start.QuadPart) / frequency.QuadPart;
    }
   
    printf("Average sprintf time: %.2f nanoseconds\n",
           (total_time / ITERATIONS) * 1e9);
}
as you mentioned to Vortex, ucrtbase does not provide a straight printf or sprintf but it does provide stdio_common_vfprintf and stdio_common_vsprintf
I ran the following from the msys2 shell
objdump.exe -d -M intel,x86-64 sprintf-timing.exe >sprintf-timing.txt
and then searched the disassembly for stdio_common_vsprintf, here are the relevant pieces
0000000140001f30 <printf>:
   140001f30: 56                    push   rsi
   140001f31: 57                    push   rdi
   140001f32: 48 83 ec 38          sub    rsp,0x38
   140001f36: 48 89 ce              mov    rsi,rcx
   140001f39: 48 89 54 24 58        mov    QWORD PTR [rsp+0x58],rdx
   140001f3e: 4c 89 44 24 60        mov    QWORD PTR [rsp+0x60],r8
   140001f43: 4c 89 4c 24 68        mov    QWORD PTR [rsp+0x68],r9
   140001f48: 48 8d 44 24 58        lea    rax,[rsp+0x58]
   140001f4d: 48 89 44 24 30        mov    QWORD PTR [rsp+0x30],rax
   140001f52: e8 99 07 00 00        call   1400026f0 <__local_stdio_printf_options>
   140001f57: 48 8b 38              mov    rdi,QWORD PTR [rax]
   140001f5a: b9 01 00 00 00        mov    ecx,0x1
   140001f5f: e8 ac 07 00 00        call   140002710 <__acrt_iob_func>
   140001f64: 48 8b 4c 24 30        mov    rcx,QWORD PTR [rsp+0x30]
   140001f69: 48 89 4c 24 20        mov    QWORD PTR [rsp+0x20],rcx
   140001f6e: 48 89 f9              mov    rcx,rdi
   140001f71: 48 89 c2              mov    rdx,rax
   140001f74: 49 89 f0              mov    r8,rsi
   140001f77: 45 31 c9              xor    r9d,r9d
   140001f7a: e8 c1 09 00 00        call   140002940 <__stdio_common_vfprintf>
   140001f7f: 90                    nop
   140001f80: 48 83 c4 38          add    rsp,0x38
   140001f84: 5f                    pop    rdi
   140001f85: 5e                    pop    rsi
   140001f86: c3                    ret
   140001f87: cc                    int3
   140001f88: cc                    int3
   140001f89: cc                    int3
   140001f8a: cc                    int3
   140001f8b: cc                    int3
   140001f8c: cc                    int3
   140001f8d: cc                    int3
   140001f8e: cc                    int3
   140001f8f: cc                    int3

0000000140001f90 <sprintf>:
   140001f90: 56                    push   rsi
   140001f91: 57                    push   rdi
   140001f92: 48 83 ec 38          sub    rsp,0x38
   140001f96: 48 89 d6              mov    rsi,rdx
   140001f99: 48 89 cf              mov    rdi,rcx
   140001f9c: 4c 89 44 24 60        mov    QWORD PTR [rsp+0x60],r8
   140001fa1: 4c 89 4c 24 68        mov    QWORD PTR [rsp+0x68],r9
   140001fa6: 48 8d 44 24 60        lea    rax,[rsp+0x60]
   140001fab: 48 89 44 24 30        mov    QWORD PTR [rsp+0x30],rax
   140001fb0: e8 3b 07 00 00        call   1400026f0 <__local_stdio_printf_options>
   140001fb5: 48 8b 08              mov    rcx,QWORD PTR [rax]
   140001fb8: 48 83 c9 02          or     rcx,0x2
   140001fbc: 48 8b 44 24 30        mov    rax,QWORD PTR [rsp+0x30]
   140001fc1: 48 89 44 24 28        mov    QWORD PTR [rsp+0x28],rax
   140001fc6: 48 c7 44 24 20 00 00 mov    QWORD PTR [rsp+0x20],0x0
   140001fcd: 00 00
   140001fcf: 48 89 fa              mov    rdx,rdi
   140001fd2: 49 c7 c0 ff ff ff ff mov    r8,0xffffffffffffffff
   140001fd9: 49 89 f1              mov    r9,rsi
   140001fdc: e8 6f 09 00 00        call   140002950 <__stdio_common_vsprintf>
   140001fe1: 90                    nop
   140001fe2: 48 83 c4 38          add    rsp,0x38
   140001fe6: 5f                    pop    rdi
   140001fe7: 5e                    pop    rsi
   140001fe8: c3                    ret
   140001fe9: cc                    int3
   140001fea: cc                    int3
   140001feb: cc                    int3
   140001fec: cc                    int3
   140001fed: cc                    int3
   140001fee: cc                    int3
   140001fef: cc                    int3

00000001400026f0 <__local_stdio_printf_options>:
   1400026f0: 48 8d 05 51 29 00 00 lea    rax,[rip+0x2951]        # 140005048 <options>
   1400026f7: c3                    ret
   1400026f8: cc                    int3
   1400026f9: cc                    int3
   1400026fa: cc                    int3
   1400026fb: cc                    int3
   1400026fc: cc                    int3
   1400026fd: cc                    int3
   1400026fe: cc                    int3
   1400026ff: cc                    int3

the only missing piece of the puzzle is 140005048 <options>

TimoVJL

#4
A that <options> is a local storage ?
#if _CRT_FUNCTIONS_REQUIRED
    // This function must not be inlined into callers to avoid ODR violations.  The
    // static local variable has different names in C and in C++ translation units.
    _Check_return_ _Ret_notnull_
    _CRT_INLINE_PURE_SECURITYCRITICAL_ATTRIBUTE
    __declspec(noinline) __inline unsigned __int64* __CRTDECL __local_stdio_printf_options(void)
    {
        static unsigned __int64 _OptionsStorage;
        return &_OptionsStorage;
    }

    // This function must not be inlined into callers to avoid ODR violations.  The
    // static local variable has different names in C and in C++ translation units.
    _Check_return_ _Ret_notnull_
    _CRT_INLINE_PURE_SECURITYCRITICAL_ATTRIBUTE
    __declspec(noinline) __inline unsigned __int64* __CRTDECL __local_stdio_scanf_options(void)
    {
        static unsigned __int64 _OptionsStorage;
        return &_OptionsStorage;
    }
#endif

#if defined _M_CEE && !defined _M_CEE_PURE
    #pragma managed(pop)
#endif

#define _CRT_INTERNAL_LOCAL_PRINTF_OPTIONS (*__local_stdio_printf_options())
#define _CRT_INTERNAL_LOCAL_SCANF_OPTIONS  (*__local_stdio_scanf_options ())



#define _CRT_INTERNAL_PRINTF_LEGACY_VSPRINTF_NULL_TERMINATION (1ULL << 0)
#define _CRT_INTERNAL_PRINTF_STANDARD_SNPRINTF_BEHAVIOR       (1ULL << 1)
#define _CRT_INTERNAL_PRINTF_LEGACY_WIDE_SPECIFIERS           (1ULL << 2)
#define _CRT_INTERNAL_PRINTF_LEGACY_MSVCRT_COMPATIBILITY      (1ULL << 3)
#define _CRT_INTERNAL_PRINTF_LEGACY_THREE_DIGIT_EXPONENTS     (1ULL << 4)
#define _CRT_INTERNAL_PRINTF_STANDARD_ROUNDING                (1ULL << 5)


#define _CRT_INTERNAL_SCANF_SECURECRT                   (1ULL << 0)
#define _CRT_INTERNAL_SCANF_LEGACY_WIDE_SPECIFIERS      (1ULL << 1)
#define _CRT_INTERNAL_SCANF_LEGACY_MSVCRT_COMPATIBILITY (1ULL << 2)
// CRT headers are included into some kinds of source files where only data type
// definitions and macro definitions are required but function declarations and
// inline function definitions are not.  These files include assembly files, IDL
// files, and resource files.  The tools that process these files often have a
// limited ability to process C and C++ code.  The _CRT_FUNCTIONS_REQUIRED macro
// is defined to 1 when we are compiling a file that actually needs functions to
// be declared (and defined, where applicable), and to 0 when we are compiling a
// file that does not.  This allows us to suppress declarations and definitions
// that are not compilable with the aforementioned tools.
#if !defined _CRT_FUNCTIONS_REQUIRED
    #if defined __assembler || defined __midl || defined RC_INVOKED
        #define _CRT_FUNCTIONS_REQUIRED 0
    #else
        #define _CRT_FUNCTIONS_REQUIRED 1
    #endif
#endif
May the source be with you


jack

with uppercase option D I get a bit more info
0000000140005048 <options>:
   140005048: 24 00                and    al,0x0
   14000504a: 00 00                add    BYTE PTR [rax],al
   14000504c: 00 00                add    BYTE PTR [rax],al
...

TimoVJL

We can test this too ?
...
static unsigned __int64 _OptionsStorage; // local option storage
...
psprintf(((ULONGLONG)&_OptionsStorage|_CRT_INTERNAL_PRINTF_STANDARD_SNPRINTF_BEHAVIOR), buf, sizeof(buf), fmt, NULL, vl);
...
May the source be with you

Vortex

Hi Jack,

It's easy to build and use the import library for ucrt :

Import libraries for the Universal C Runtime :

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

Universal C Runtime example :

https://forum.pellesc.de/index.php?topic=11054.0
Code it... That's all...


TimoVJL

That silly test used only ucrtbase.dll, as those Api Stub forwanders mostly point to it.
Making ucrt.lib is more demanding, as have to collect those (stupid) forwanders are mostly used for versioning, and with no code.
May the source be with you