Specification of signed integer overflow/wraparound behavior

Hi everyone,

from C/C++ I’m used to signed integer overflows being undefined behavior.

In “the (B&R) automation world” I got the impression that it is sometimes common to rely on a certain well-defined signed integer behavior. For example in reaction programs, where timestamps are signed integers and durations between them are calculated with signed integer differences. Same behavior with AsIOTimeStamp() and the calculation of duration in this example.

In a nutshell, it seems to me that we are relying on c evaluating to 1 in

a := 2147483647;  // ==DINT_MAX
b := a + 1;  // ==DINT_MIN==-2147483648 (who/what guarantees this?)
c := b - a;

where a, b, c all are of type DINT, while in C/C++ both b and c are undefined, when a, b, c are int32_t and a is set to std::numeric_limits<int32_t>::max() initially.

Question: Where is the signed integer behavior specified? Why is it safe for a programmer to rely on such behavior? (Or is it not safe, and I have a misconception here?)

EDIT: typo

A signed variable follow two’s complement binary notation.

The carry and overflow handling are inherently managed by the binary representation.

In a PLC often encoders are used, which are continue moving in one direction. The DINT value will overflow.

If in software something is calulated like;
Diff = newpos - oldpos
If an overflow happens between these two values, the difference is still correct.
newpos is overflowed value → very large negativ value
oldpos is still a large positive value

If in this case unsigned variables were used, more code would be needed to calculate the correct difference.

Hey @Lukas_Kimme,
I think I understand what you’re inquiring about. Let me share my thoughts.

from C/C++ I’m used to signed integer overflows being undefined behavior

while in C/C++ both b and c are undefined

I think “signed integer overflow” itself, is not undefined behavior.
I think specific signed integer overflow expressions with compiler optimization can create undefined behavior.

From the link you shared:

Because correct C++ programs are free of undefined behavior, compilers may produce unexpected results

Let’s look at their example.

x + 1 > x

In mathematics, this expression always evaluates to true.
In computers, following order of operations, this expression is always true unless x is INT32_MAX. In which case signed integer overflow will cause the expression to result as false.
Finally, for computer programs compiled with optimization, the expression may be optimized to

1 > 0

and finally just 1 or true. In this case, the expression will never be false because the compiler optimized the logic.

Certain signed integer expression will produce different results depending on compiler optimization settings, thus undefined behavior.

These ambiguous expressions could be avoided.

y = x + 1;
return y > x;

Your example with a, b, and c is not ambiguous, a good thing.

You can see your compiler settings in Automation Studio, which does use some optimization by default. It’s unlikely -O0 will introduce undefined behavior, but the GCC compiler documentation can confirm.

Best thing you can do is write non-ambiguous code, and unit test edge cases. Looks like you’re already doing that. :grinning_face:

Thanks @corne.geerts and @tylermatijevich. However, your answers do not answer this question for me.

Regarding the question whether signed integer overflow is or is not UB in C++, cppreference states explicitly and without further restrictions:

Some examples of undefined behavior are data races, memory accesses outside of array bounds, signed integer overflow, […] etc.

Hi Lukas,

i see the UB in your example if that would be c++. I am not sure if this is an UB in ST, too.
An unabibgous code would be to check an overflow by yourself.

but I do not see an UB in your cited example

This is calculation with two’s complement as Corne mentioned since AsIOTimeStamp() should always return valid integers…

Thanks Christoph.

Let’s assume a first call to AsIOTimeStamp() returned a = 2147483647 = DINT_MAX and a second call returned b = -2147483648 = DINT_MIN. Now, calculating b-a or a-b in C/C++ would be UB, since the actual result overflows and is not representable in a 32bit signed integer. Another quote, this time from Wikipedia: In C, unsigned integer overflow is defined to wrap around, while signed integer overflow causes undefined behavior.

Of course, I can assume that two’s complement will be used and everything is well behaved. But why is this a valid assumption? Where is this specified?

BTW: The B&R chat bot does not know anything about UB in structured text (and it did not find anything related to two’s complement)

1 Like

Hi Lukas,

your are right. And thank you for bringing this to our attention.

I assume that a nice person in the background has set the right options for the gcc:

Overflow with signed integers

if you add -ftrapv to the compiler options

you actually get a problem when you perform this operations.

if you add -fwrapv (or if you do not use an option) then the two’s complement is apparently used.

1 Like

Hi,

when AS compiles a C/C++ program, the following compiler options are set additionally to those visible in the AS project:
-fvisibility=hidden -fsigned-char -fwrapv -fno-builtin -fno-diagnostics-show-caret

-fwrapv defines the behavior of singed integer overflow: GNU code gen options

2 Likes

Great Martin and Christoph, this clarifies things quite a bit.

@Martin.Schrott where did you get this information from, where is this documented? And since you mention C/C++ explicitly: is there similar information for ST?

In my initial posting I also asked about reACTION, where I saw time stamps being used in a similar fashion. I don’t think the gcc is used in this case, however, I assume that similar measures are in place to ensure correct wrap around behavior.

@B&R: I would appreciate if such information was available from the help.

I don’t think this is documented.
I found it by checking <project_folder>/Temp/Objects/CP1586Sim/X20CP1686X/LastBuildOptions.xml
then looked for a C task and found “\AS\gnuinst\V11.3.0\6.0\lib\gcc\i686-elf\11.3.0\brelf.specs” is used for compiler options.
In that file the options are listed.

I can only assume that with ST it is handled in the same way, otherwise there would have been problems reported over the last 20 years :wink: