Problem
When a CONFIGURATION declares a TASK whose INTERVAL does not convert to a
whole number of nanoseconds, STruC++ generates a floating-point value carrying
an integer (LL) suffix for the task's interval_ns field. This is not a
valid C++ literal, so the generated .cpp fails to compile with g++/clang.
Environment
- STruC++ version 0.6.2 (win64,
strucpp.exe)
- Compiler: g++ 14 (MSYS2 UCRT64),
-std=c++17
- Also reproduces with
-fext-numeric-literals (that flag does not fix it)
Impact
Blocks compilation of the generated code entirely. Any TASK whose INTERVAL
is not a whole number of nanoseconds hits this — e.g. a 60 Hz rate expressed as
a 16.6667 ms task period.
Reproduction
min.st:
PROGRAM PLC_PRG
VAR
x : INT;
END_VAR
x := x + 1;
END_PROGRAM
CONFIGURATION MainConfig
RESOURCE MainResource ON PLC
TASK MainTask (INTERVAL := T#16.6667ms, PRIORITY := 0);
PROGRAM MainInstance WITH MainTask : PLC_PRG;
END_RESOURCE
END_CONFIGURATION
Compile to C++:
strucpp min.st -o min.cpp --no-default-libs
Actual result
min.cpp contains (constructor of Configuration_MAINCONFIG):
tasks_storage[0] = TaskInstance("MAINTASK", 16666699.999999998LL, 0,
&task_programs_storage[0], 1);
Compiling min.cpp fails:
min.cpp:33:49: error: unable to find numeric literal operator 'operator""LL'
33 | tasks_storage[0] = TaskInstance("MAINTASK", 16666699.999999998LL, 0, ...);
| ^~~~~~~~~~~~~~~~~~~~
note: use '-fext-numeric-literals' to enable more built-in suffixes
(The suggested -fext-numeric-literals does not help — this is a malformed
literal, not a missing user-defined suffix.)
Code Locations
No response
Fix
Root cause
STruC++ appears to compute the interval as
literal_value * scale in floating point (here 16.6667 * 1e6) and then append
the LL suffix to the resulting double without converting it to an integer.
16.6667 * 1e6 is not exactly representable, so it lands on
16666699.999999998, and 16666699.999999998LL is emitted verbatim.
The bug is intermittent because it only surfaces when the nanosecond value is not
exactly representable as a floating-point integer:
| INTERVAL |
emitted literal |
valid? |
T#16.6667ms |
16666699.999999998LL |
❌ no |
T#16.667ms |
16667000.000000002LL |
❌ no |
T#33.3333ms |
33333300LL |
✅ yes |
T#10ms |
10000000LL |
✅ yes |
T#0.5s |
500000000LL |
✅ yes |
T#1000ms |
1000000000LL |
✅ yes |
Suggested fix
Truncate/round the computed nanosecond value to an integer before formatting
the literal, so an int64_t-typed field always receives an integer token.
Equivalently: build the interval in integer arithmetic (accumulate each
time-component's contribution as int64_t nanoseconds) rather than multiplying a
double and casting at the literal-emission step.
Acceptance
TaskInstance::interval_ns is declared int64_t (runtime/include/iec_std_lib.hpp):
struct TaskInstance {
...
int64_t interval_ns; ///< Execution interval in nanoseconds (0 = event-driven)
...
TaskInstance(const char* n, int64_t interval, int32_t prio, ...);
};
so the emitted argument should be an integer literal, e.g. 16666700LL
(or 16666699LL). A 1 ns rounding difference is immaterial.
Problem
When a
CONFIGURATIONdeclares aTASKwhoseINTERVALdoes not convert to awhole number of nanoseconds, STruC++ generates a floating-point value carrying
an integer (
LL) suffix for the task'sinterval_nsfield. This is not avalid C++ literal, so the generated
.cppfails to compile with g++/clang.Environment
strucpp.exe)-std=c++17-fext-numeric-literals(that flag does not fix it)Impact
Blocks compilation of the generated code entirely. Any
TASKwhoseINTERVALis not a whole number of nanoseconds hits this — e.g. a 60 Hz rate expressed as
a 16.6667 ms task period.
Reproduction
min.st:Compile to C++:
Actual result
min.cppcontains (constructor ofConfiguration_MAINCONFIG):Compiling
min.cppfails:(The suggested
-fext-numeric-literalsdoes not help — this is a malformedliteral, not a missing user-defined suffix.)
Code Locations
No response
Fix
Root cause
STruC++ appears to compute the interval as
literal_value * scalein floating point (here16.6667 * 1e6) and then appendthe
LLsuffix to the resultingdoublewithout converting it to an integer.16.6667 * 1e6is not exactly representable, so it lands on16666699.999999998, and16666699.999999998LLis emitted verbatim.The bug is intermittent because it only surfaces when the nanosecond value is not
exactly representable as a floating-point integer:
T#16.6667ms16666699.999999998LLT#16.667ms16667000.000000002LLT#33.3333ms33333300LLT#10ms10000000LLT#0.5s500000000LLT#1000ms1000000000LLSuggested fix
Truncate/round the computed nanosecond value to an integer before formatting
the literal, so an
int64_t-typed field always receives an integer token.Equivalently: build the interval in integer arithmetic (accumulate each
time-component's contribution as
int64_tnanoseconds) rather than multiplying adoubleand casting at the literal-emission step.Acceptance
TaskInstance::interval_nsis declaredint64_t(runtime/include/iec_std_lib.hpp):so the emitted argument should be an integer literal, e.g.
16666700LL(or
16666699LL). A 1 ns rounding difference is immaterial.