Fixture 123

sizeof semantics

C · 5 functions · 4 lanes · 18 of 20 function-lanes behave identically

2 of 4 lanes have a function that returns a different result after decompilation: clang-O0 (4/5), gcc-O0 (4/5).

sizeof does not evaluate its operand (the sole exception being a VLA), so a call written inside sizeof is never emitted. The result is a compile-time constant of type size_t.

tests/decompiler_fixtures/src/123_sizeof_semantics.c source
#include <stdint.h>

/* sizeof does not evaluate its operand (the sole exception being a VLA), so a
 * call written inside sizeof is never emitted. The result is a compile-time
 * constant of type size_t. */

/* Exported rather than static: `sizeof` never evaluates the call below, so a
 * static definition would be unreferenced and clang rejects that under -Werror.
 * External linkage forces it to be emitted while leaving the point intact. */
__attribute__((noinline)) int32_t sizeof_probe_sets_flag(int32_t *flag) {
    if (flag == 0) {
        return -1;
    }
    *flag = 1;
    return 0;
}

__attribute__((noinline)) int32_t
sizeof_does_not_evaluate(int32_t *flag) {
    if (flag == 0) {
        return -1;
    }
    *flag = 0;
    /* The call is not invoked; only its return type is inspected, so the flag
     * stays 0. */
    return (int32_t)sizeof(sizeof_probe_sets_flag(flag)) * 10 + *flag;
}

__attribute__((noinline)) int32_t sizeof_array_versus_pointer(void) {
    int32_t array[8];
    int32_t *pointer = array;
    /* sizeof(array) is the whole object; sizeof(pointer) is one address. The
     * element-count division is written only for the array, because both
     * compilers reject `sizeof(pointer) / sizeof(element)` outright -- the
     * gotcha this documents is now a diagnostic, so the sizes are compared
     * directly instead. */
    return (int32_t)(sizeof(array) / sizeof(array[0])) * 100 +
           (int32_t)sizeof(pointer);
}

__attribute__((noinline)) int32_t sizeof_after_decay(const int32_t values[8]) {
    /* A parameter declared as an array is really a pointer. `sizeof(values)`
     * would prove it but is a hard error under -Werror on both compilers, so
     * the decay is demonstrated by arithmetic instead: advancing the parameter
     * by one steps one element, exactly as a pointer does, and the declared
     * bound 8 has no effect on it at all. */
    const int32_t *stepped;
    if (values == 0) {
        return -1;
    }
    stepped = values + 1;
    return (int32_t)(stepped - values) * 100 + (int32_t)sizeof(values[0]);
}

__attribute__((noinline)) int32_t sizeof_vla_is_evaluated(int32_t count) {
    if (count < 1 || count > 16) {
        return -1;
    }
    {
        int32_t scratch[count];
        scratch[0] = count;
        /* This sizeof IS a runtime computation. */
        return (int32_t)(sizeof(scratch) / sizeof(scratch[0])) + scratch[0];
    }
}

Recovered C

Generated by glaurung decompile --style decbench at b47f6b43. baseline.json records the result after recompiling the C and calling it beside the original with seeded inputs.

clang -O0

4/5
sizeof_after_decay pass 11 lines
// glaurung: sizeof_after_decay @ 0x11a0
int32_t sizeof_after_decay(const int32_t * arg0) {
    int * stepped;
    // x86-64 prologue: save rbp
    if ((arg0 != 0)) {
        stepped = (int *)((arg0 + 1));
        return (unsigned int)(((((long)(((long)stepped - (long)arg0)) >> 2) * 100) + 4));
    } else {
        return (unsigned int)(-1);
    }
}
sizeof_array_versus_pointer pass 9 lines
// glaurung: sizeof_array_versus_pointer @ 0x1180
__attribute__((no_stack_protector)) int32_t sizeof_array_versus_pointer(void) {
    int * pointer;
    unsigned char local_20[32];
    // x86-64 prologue: save rbp
    pointer = (int *)&local_20[0];
    // x86-64 epilogue: restore rbp
    return 808;
}
sizeof_does_not_evaluate pass 10 lines
// glaurung: sizeof_does_not_evaluate @ 0x1140
int32_t sizeof_does_not_evaluate(int32_t * arg0) {
    // x86-64 prologue: save rbp
    if ((arg0 != 0)) {
        *(int *)((long)arg0) = 0;
        return (unsigned int)(((unsigned long)((unsigned int)(*(int *)((long)arg0))) + 40));
    } else {
        return (unsigned int)(-1);
    }
}
sizeof_probe_sets_flag pass 10 lines
// glaurung: sizeof_probe_sets_flag @ 0x1100
int32_t sizeof_probe_sets_flag(int32_t * arg0) {
    // x86-64 prologue: save rbp
    if ((arg0 != 0)) {
        *(int *)((long)arg0) = 1;
        return 0;
    } else {
        return (unsigned int)(-1);
    }
}
sizeof_vla_is_evaluated fail 26 lines
// glaurung: sizeof_vla_is_evaluated @ 0x11f0
__attribute__((no_stack_protector)) int32_t sizeof_vla_is_evaluated(int32_t arg0) {
    unsigned long __vla_expr0;
    void * local_10;
    int local_4;
    unsigned char stack_1[32];
    long var5;
    // x86-64 prologue: save rbp, frame 32 bytes
    if (((long)(arg0) < 1)) {
        local_4 = -1;
        // x86-64 epilogue: restore rbp
        return (unsigned int)(local_4);
    }
    if (((((unsigned long)((unsigned int)(arg0)) == 16) | ((long)(arg0) < 16)) == 0)) {
        local_4 = -1;
        // x86-64 epilogue: restore rbp
        return (unsigned int)(local_4);
    }
    local_10 = &stack_1[0];
    var5 = (long)((&stack_1[0] - ((((unsigned long)((unsigned int)(arg0)) * 4) + 15) & -16)));
    __vla_expr0 = (unsigned int)(arg0);
    *(int *)((var5)) = arg0;
    local_4 = (((unsigned long)(((unsigned long)((unsigned int)(arg0)) << 2)) >> 2) + *(int *)((var5)));
    // x86-64 epilogue: restore rbp
    return (unsigned int)(local_4);
}

gcc -O0

4/5
sizeof_after_decay pass 11 lines
// glaurung: sizeof_after_decay @ 0x11b1
int32_t sizeof_after_decay(const int32_t * arg0) {
    int * stepped;
    // x86-64 prologue: save rbp
    if ((arg0 != 0)) {
        stepped = (int *)((arg0 + 1));
        return (unsigned int)(((((long)(((long)stepped - (long)arg0)) >> 2) * 100) + 4));
    } else {
        return 0xffffffff;
    }
}
sizeof_array_versus_pointer pass 17 lines
// glaurung: sizeof_array_versus_pointer @ 0x1173
int32_t sizeof_array_versus_pointer(void) {
    extern __attribute__((noreturn)) void __stack_chk_fail(void);
    int * pointer;
    unsigned char local_30[32];
    long local_8;
    long ret;
    // x86-64 prologue: save rbp, frame 64 bytes
    local_8 = (long)(0x28);
    pointer = (int *)&local_30[0];
    ret = 808;
    if ((local_8 != 0x28)) {
        __stack_chk_fail();
    }
    // x86-64 epilogue: restore rbp
    return ret;
}
sizeof_does_not_evaluate pass 10 lines
// glaurung: sizeof_does_not_evaluate @ 0x1144
int32_t sizeof_does_not_evaluate(int32_t * arg0) {
    // x86-64 prologue: save rbp
    if ((arg0 != 0)) {
        *(int *)((long)arg0) = 0;
        return (unsigned int)(((unsigned long)((unsigned int)(*(int *)((long)arg0))) + 40));
    } else {
        return 0xffffffff;
    }
}
sizeof_probe_sets_flag pass 10 lines
// glaurung: sizeof_probe_sets_flag @ 0x1119
int32_t sizeof_probe_sets_flag(int32_t * arg0) {
    // x86-64 prologue: save rbp
    if ((arg0 != 0)) {
        *(int *)((long)arg0) = 1;
        return 0;
    } else {
        return 0xffffffff;
    }
}
sizeof_vla_is_evaluated fail 42 lines
// glaurung: sizeof_vla_is_evaluated @ 0x11eb
int32_t sizeof_vla_is_evaluated(int32_t arg0) {
    extern __attribute__((noreturn)) void __stack_chk_fail(void);
    long local_10;
    long local_18;
    long local_8;
    long ret;
    long rsp;
    long var23;
    long var27;
    long var37;
    long var4;
    long var5;
    // x86-64 prologue: save rbp, frame 48 bytes
    local_8 = (long)(0x28);
    if (((((unsigned long)((unsigned int)(arg0)) == 0) | ((long)(arg0) < 0)) || (((unsigned long)((unsigned int)(arg0)) != 16) && (16 <= (long)(arg0))))) {
        ret = 0xffffffff;
    } else {
        var4 = rsp;
        var5 = (unsigned long)((unsigned int)(arg0));
        local_18 = ((long)(arg0) - 1);
        var23 = (((unsigned long)(((((unsigned __int128)(unsigned long)(0) << 64) | (unsigned long)(((16 - 1) + (0 + ((long)((int)((unsigned long)((unsigned int)(arg0)))) * 4))))) / (unsigned long)(16)))) * 16);
        var27 = (rsp - (var23 & -4096));
        while ((rsp != var27)) {
            rsp = (rsp - 4096);
        }
        rsp = (rsp - (unsigned long)((unsigned int)((var23 & 4095))));
        if (((unsigned long)((unsigned int)((var23 & 4095))) != 0)) {
            var37 = (((unsigned long)((unsigned int)((var23 & 4095))) - 8) + rsp);
            *(long *)((var37)) = *(long *)((var37));
        }
        local_10 = (((unsigned long)((rsp + 3)) >> 2) << 2);
        *(int *)((local_10)) = arg0;
        ret = (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(*(int *)(local_10))) + (unsigned long)((unsigned int)(((unsigned long)(((long)((int)(var5)) << 2)) >> 2))))));
        rsp = var4;
    }
    if ((local_8 != 0x28)) {
        __stack_chk_fail();
    }
    // x86-64 epilogue: restore rbp
    return ret;
}

clang -O2

5/5
sizeof_after_decay pass 4 lines
// glaurung: sizeof_after_decay @ 0x1150
int32_t sizeof_after_decay(const int32_t * arg0) {
    return (unsigned int)(((unsigned int)((0 - ((unsigned long)((long)arg0) < (unsigned long)(1)))) | 104));
}
sizeof_array_versus_pointer pass 4 lines
// glaurung: sizeof_array_versus_pointer @ 0x1140
int32_t sizeof_array_versus_pointer(void) {
    return 808;
}
sizeof_does_not_evaluate pass 8 lines
// glaurung: sizeof_does_not_evaluate @ 0x1120
int32_t sizeof_does_not_evaluate(int32_t * arg0) {
    if ((arg0 == 0)) {
        return 0xffffffff;
    }
    *(int *)(((long)arg0)) = 0;
    return 40;
}
sizeof_probe_sets_flag pass 8 lines
// glaurung: sizeof_probe_sets_flag @ 0x1100
int32_t sizeof_probe_sets_flag(int32_t * arg0) {
    if ((arg0 == 0)) {
        return 0xffffffff;
    }
    *(int *)(((long)arg0)) = 1;
    return 0;
}
sizeof_vla_is_evaluated pass 4 lines
// glaurung: sizeof_vla_is_evaluated @ 0x1160
int32_t sizeof_vla_is_evaluated(int32_t arg0) {
    return (((unsigned long)(0xfffffff0) <= (unsigned long)((unsigned long)((unsigned int)((arg0 - 17))))) ? (unsigned long)((unsigned int)((arg0 + arg0))) : 0xffffffff);
}

gcc -O2

5/5
sizeof_after_decay pass 4 lines
// glaurung: sizeof_after_decay @ 0x1150
int32_t sizeof_after_decay(const int32_t * arg0) {
    return (unsigned int)(((unsigned int)((0 - ((unsigned long)((long)arg0) < (unsigned long)(1)))) | 104));
}
sizeof_array_versus_pointer pass 4 lines
// glaurung: sizeof_array_versus_pointer @ 0x1140
int32_t sizeof_array_versus_pointer(void) {
    return 808;
}
sizeof_does_not_evaluate pass 8 lines
// glaurung: sizeof_does_not_evaluate @ 0x1120
int32_t sizeof_does_not_evaluate(int32_t * arg0) {
    if ((arg0 == 0)) {
        return 0xffffffff;
    }
    *(int *)(((long)arg0)) = 0;
    return 40;
}
sizeof_probe_sets_flag pass 8 lines
// glaurung: sizeof_probe_sets_flag @ 0x1100
int32_t sizeof_probe_sets_flag(int32_t * arg0) {
    if ((arg0 == 0)) {
        return 0xffffffff;
    }
    *(int *)(((long)arg0)) = 1;
    return 0;
}
sizeof_vla_is_evaluated pass 7 lines
// glaurung: sizeof_vla_is_evaluated @ 0x1160
int32_t sizeof_vla_is_evaluated(int32_t arg0) {
    if (((unsigned long)(15) < (unsigned long)((unsigned long)((unsigned int)((arg0 - 1)))))) {
        return 0xffffffff;
    }
    return (unsigned int)((arg0 + arg0));
}

← 213 fixtures