0

I have been trying to solve a bug in work for a while and finally I found it. It turns out that one function was passing a variable of size uint16_t to another function which accepts a variable of size uint8_t, even without any explicit casting.

This was intermittently causing an assert on lowers layers within the software whenever an invalid ID was detected.

The below code shows a much more simplified version of what happened.

The thing is that the caller function can't have an ID value greater than 0xFF even tho it has size of uint16_t. So, passing the value to the callee function almost all of the time, the value is valid.

The interesting thing is that the problem / assert only happened when size optimisation was enabled.

I'm interested to know what exactly could have happened here. I'm trying to understand it properly. It seems that the variable being passed is sometimes greater than 0xFF (but it shouldn't be) and then must get truncated when passed to the function resulting in an invalid ID.

Also, What is the effect of simply passing the variable like is done below versus casting the variable to uint8_t , like func((uint8_t)ID); ?

I'm surprised the compiler didn't even warn on this in my case.

EDITED : Code example with value of variable causing confusion

Many thanks in advance.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

void func(uint8_t);

int main()
{
    uint16_t ID;
    // more code
    // ID gets assigned a value from 0 to 0xFF somewhere 
    //....
    func(ID);
    return 0;
}


void func(uint8_t ID )
{
    // Processing of ID
    //........
    //........

    printf("%x\n", ID);
}
Engineer999
  • 3,683
  • 6
  • 33
  • 71
  • optimization, maybe inlining so there's no downcast/truncation? did you use gcc? with -Os option? – Jean-François Fabre Jan 22 '18 at 16:11
  • amazing that there is no warning - at least not on ideone – pm100 Jan 22 '18 at 16:12
  • What compiler and platform are you using? – Jonny Schubert Jan 22 '18 at 16:12
  • cannot reproduce with gcc, even with inline or such. I get 0x44 as expected. You should disassemble that part to see what's going on. – Jean-François Fabre Jan 22 '18 at 16:16
  • Ok in work the compiler is for a freescale microcontroller. The above code however was compiled on codeblocks for windows with minGW – Engineer999 Jan 22 '18 at 16:18
  • and the output of the above code with MinGW is?? – Jean-François Fabre Jan 22 '18 at 16:18
  • 1
    @Jean-FrançoisFabre , what are you trying to reproduce? I should have clarified maybe, i'm just giving the code above as an example. – Engineer999 Jan 22 '18 at 16:19
  • I mean: is your mingw compiled code printing 44 or 5544? can you reproduce the issue on a wide-spread compiler like MinGW? because I can't – Jean-François Fabre Jan 22 '18 at 16:20
  • @JonnySchubert : -------------- Build: Debug in Cast_Integers (compiler: GNU GCC Compiler)--------------- mingw32-gcc.exe -Wall -g -c C:\Users\user\Documents\codeblocks_projects\Cast_Integers\main.c -o obj\Debug\main.o mingw32-g++.exe -o bin\Debug\Cast_Integers.exe obj\Debug\main.o Output file is bin\Debug\Cast_Integers.exe with size 28.49 KB Process terminated with status 0 (0 minute(s), 0 second(s)) 0 error(s), 0 warning(s) (0 minute(s), 0 second(s)) – Engineer999 Jan 22 '18 at 16:24
  • @Jean-FrançoisFabre There is no issue with the above code. I am just giving an example as to show a simplified version of what I mean by passing an integer of bigger size. The real problem was more complex. yes the printf() prints 0x44 for me too – Engineer999 Jan 22 '18 at 16:26
  • @Engineer999.: Your question is why compiler doesn;t warn in the first place? – user2736738 Jan 22 '18 at 16:28
  • @coderredoc . My question is to ask if someone would have an idea as to why optmisation would cause an incorrect value which is greater than 0xFF to be passed to the function. It's not related to the code above. Sorry for confusion – Engineer999 Jan 22 '18 at 16:31
  • that function is never going to get a value > 0xFF – pm100 Jan 22 '18 at 16:32
  • @Engineer999.: I have tried with several optimization level in gcc - the same compiler collection whose `g++` is being used by you....no abnormal behavior anytime. – user2736738 Jan 22 '18 at 16:33
  • I've edited the code in the question. I'm questioning now should I have put any code there at all as it's causing confusion. It is just for an example to show. It was not the real-world problem. – Engineer999 Jan 22 '18 at 16:38

2 Answers2

3

If you are using gcc, -Wconversion will generate the following warning for this code:

$ gcc -Wconversion tmp.c tmp.c: In function ‘main’: tmp.c:11:10: warning: conversion to ‘uint8_t {aka unsigned char}’ from ‘uint16_t {aka short unsigned int}’ may alter its value [-Wconversion] func(ID);

EDIT: I don't know if this flag is supported by MinGW's gcc.

EDIT2: To answer the other part of the question - the effect is truncation, and it will truncate whether or not you cast the variable with func((uint8_t)ID) (clarified).

dbeer
  • 6,963
  • 3
  • 31
  • 47
  • I don't understand the DV. This doesn't answer but provide means to check against this. Well, maybe it prints a lot of false positives. – Jean-François Fabre Jan 22 '18 at 16:24
  • and yes, Wconversion is supported by MinGW gcc compiler – Jean-François Fabre Jan 22 '18 at 16:24
  • I just edited my response to answer. It seemed like an easier means of identifying this mistake was a main concern of the question. – dbeer Jan 22 '18 at 16:30
  • 1
    "... the effect is truncation, and there is no difference whether or not you cast the variable with `func((uint8_t)ID)`" That's a bit unclear. Does the "no effect" mean the cast doesn't effect the *warning*, or the cast doesn't change the truncation? I'd hope using the cast would result in no warning - only truncation *without* a cast would generate a warning. – Andrew Henle Jan 22 '18 at 16:33
  • @AndrewHenle noted. Thanks – dbeer Jan 22 '18 at 16:34
0

Whatever you have been mentioned - there is no optimization level that would cause the standard behavior to deviate. By that I mean even if you pass a value that is not possible to hold in uint8_t type variable it would simply wrap on the modulo UINT8_MAX+1 and then that value is being printed.

The compiler you used by default didn't have the switch enabled which will enable the type conversion error. After you enable that then the casting would matter. If for any level that non-cast passing generates warning the casting one wouldn't. That is where the explicit casting comes into picture.

In your case when there was no switches enabled it simply didn't matter. whether you cast or not.

Your above code being tested in gcc with all possible optimization level enabled causes the output of 44 which is exactly what it should be after the above mentioned modular arithmetic.

In case you saw any behavior that deviates from the current one - it should be under close scrutiny that whether there is any intermediate action that is changing the value - but even if that happens it wont ever hold the value greater than UINT8_MAX.

user2736738
  • 30,591
  • 5
  • 42
  • 56
  • Would optimisation cause the variable in the caller function to be greater than 0xFF at all?, even tho it shouldn't be. This is what i'm guessing happened and so the value in the callee function would then be nonsense and an invalid ID. When I made the fix so that I pass a uint8_t instead, it fixed the problem. That is my confusion – Engineer999 Jan 22 '18 at 16:43
  • @Engineer999.: The simple thing - when I checked the generated assembly on the case where ID is not used in caller function then `0x44` is being used entirely but if we use it in caller then ofc `0x5544` was also used. I dont know if I get your question but this is what you can see for yourself (this is compiled with flag `-O3`) – user2736738 Jan 22 '18 at 17:00