Weird warning with printf and integers

82 Views Asked by At

I'm becoming crazy with the following code and the GCC compiler (v11.4 to x86_64 on Linux):

static void disasm_TESTSX_X(char *__buffer, test_inst_t *inst) {
    __buffer += sprintf(__buffer, "testsx %lx", ((uint32_t)(((int32_t)(TEST_TESTSX_X_x_i)))));
}

static void disasm_TESTW_X(char *__buffer, test_inst_t *inst) {
        __buffer += sprintf(__buffer, "testw %lx", (((uint32_t)(TEST_TESTW_X_x_i)) + 1L));

}

First form gives me a warning at compilation time (I should use %x) while the second does not. TEST_TESTW_X_x_i is of type uint8_t. Yet, both expressions are explicitly converted to uint32_t (code is not very readable as it is generated code).

Thanks to anyone that could help me.

3

There are 3 best solutions below

0
Eric Postpischil On

"testsx %lx" … I should use %x…

Neither is correct for converting a uint32_t value. To convert a uint32_t to hexadecimal, change "testsx %lx" to "testsx %" PRIx32 and insert #include <inttypes.h>.

uint32_t may be unsigned in some C implementations, unsigned long in others, and some other type in yet others. So neither %x nor %lx will be correct in every C implementation. PRIx32, defined in <inttypes.h>, will be replaced by a string literal with a correct conversion specifier for uint32_t in the C implementation being used.

Yet, both expressions are explicitly converted to uint32_t…

No, they are not. The argument in the second case is (((uint32_t)(TEST_TESTW_X_x_i)) + 1L), which is the sum of a uint32_t expression and 1L. In a C implementation where long is wider than uint32_t, the left operand of + will be converted to long, and the type of the result will be long.

0
John Bollinger On

First form gives me a warning at compilation time (I should use %x) while the second does not. TEST_TESTW_X_x_i is of type uint8_t. Yet, both expressions are explicitly converted to uint32_t (code is not very readable as it is generated code).

Yes, both expansions of the macro are converted to type uint32_t, but only in the first case is that result being passed to printf. In the second case, it is that uint32_t result plus 1L. When the sum is evaluated, the operands are converted to a common type, and the result has that type. It should not be too surprising, then, that different conversion specifiers are appropriate for the different cases.

In particular, in the event that your uint32_t is the same type as unsigned int (relatively common), correctly-matching printf conversion specifiers will not have any width specifier. That is, you would use %x instead of %lx. Technically, that applies even if long unsigned int is also 32 bits wide.

In that case, however, the type of integer constant 1L, which is long int, has higher integer conversion rank than uint32_t. Therefore, when the addition is performed, the type chosen for the result will not be uint32_t. It will be long int if that type can represent all the values representable by uint32_t, or unsigned long int otherwise. (This is an application of the "usual arithmetic conversions".) The conversion specifier %lx is consistent with type unsigned long int. It is not consistent with [signed] long int, but it is conceivable that the compiler would not warn about that mismatch.

Summary:

  • your uint32_t is probably the same as unsigned int. In this case, %x is a correct printf conversion specifier for (uint32_t)(((int32_t)(TEST_TESTSX_X_x_i))), but %lx is not.

  • Given uint32_t the same as unsigned int, the type of ((uint32_t)(TEST_TESTW_X_x_i)) + 1L) is either unsigned long int (if that is also a 32-bit type) or otherwise long int. %lx is a correct printf conversion specifier for unsigned long int, but %x is not. Neither is correct for long int, but you might nevertheless not get an warning about %lx in that case.


Additionally, when you are doing formatted I/O involving the explicit-width integer types, it is to your advantage to use the provided macros for expressing the appropriate conversion specifiers. For example:

sprintf(__buffer, "testsx %" PRIx32, (uint32_t)(TEST_TESTSX_X_x_i));

If you can't be sure about the type of the expression whose value you are trying to format, however, then it is wise to convert it to a known type. In this case, that might be something like

sprintf(__buffer, "testw %" PRIx32, (uint32_t)((TEST_TESTW_X_x_i) + 1L));

0
chux - Reinstate Monica On

A bit of re-hash of existing answers, but plus a tip.

Use matching specifiers and types with *printf()

"testsx %lx", ((uint32_t)(((int32_t)(TEST_TESTSX_X_x_i)))) attempts to print a uint32_t with an unsigned long specifier. Those are not certainly the same.

Avoid math in *printf() arguments that result in an ambigous type.

(((uint32_t)(TEST_TESTW_X_x_i)) + 1L adds a uint32_t with a long creating a sum of either type uint32_t or long.


Tip: Get the type circus done before printing.

static void disasm_TESTW_X(char *__buffer, test_inst_t *inst) {
    unsigned long x = ((uint32_t)(TEST_TESTW_X_x_i)) + 1L;  // Better with + 1u
    __buffer += sprintf(__buffer, "testw %lx", x);
}

BTW, __buffer is updated, yet results discarded.

Perhaps due to problem simplification. I'd expect:

static char *disasm_TESTW_X(char *__buffer, test_inst_t *inst) {
    unsigned long x = ((uint32_t)(TEST_TESTW_X_x_i)) + 1L;  // Better with + 1u
    __buffer += sprintf(__buffer, "testw %lx", x);
    return __buffer;
}