Die static_assert funktionieren so aber nicht.
Wie bekomme ich das hin das wenn ein bestimmtes Array ein ungültigen Wert besitzt (in dem Fall der Wert ist größer als 5) nicht kompiliert wird.
Natürlich geht es nicht genau um diesen Code sondern um das Prinzip.
Hintergrund ist eher das eine gesharede Lib gibt, die jeder Anpassen kann, es ist aber wichtig, das wenn jemand da was hinzufügt was ungültig ist, es gar nicht erst kompiliert. Viele Dinge sind leider historisch gewachsen und soetwas wäre die leichteste Lösung...
constexpr ist mit einem C23-fähigem Compiler zwar möglich, aber im static_assert funktioniert das dann trotzdem nicht.
Liest man die Kommentare zu constexpr in C, dann dürfte das so gewollt sein. C soll halt weiterhin ein besserer Macro-Assembler bleiben, bei dem der Programmier jegliche Freiheiten hat, beliebige Fehler zu machen.
Hintergrund ist eher das eine gesharede Lib gibt, die jeder Anpassen
kann, es ist aber wichtig, das wenn jemand da was hinzufügt was ungültig
ist, es gar nicht erst kompiliert.
Notfalls halt einen test dafür schreiben. Kleines Programm was die Library dazulinkt und die Einträge prüft.
Muss ja kein vollausgewachsenes CI/CD-Setup werden, kriegt man auch so in ein Makefile gebastelt, dass der Buildprozess abbricht.
Weil man mit const in C keine echten Compilezeit-Konstanten definiert, sondern nur Variablen, die man nicht ändern kann. Und auf Variablen kann man kein static_assert anwenden.
so sollte das im Grunde funktionieren. Allerdings werden Fehler nur auf
der stderr ausgeben. Einen Compilerabbruch wie bei C++ gibt es nicht.
Ja, die Prüfung wird dann erst zur Laufzeit durchgeführt, mit allen Nachteilen, die das mit sich bringt. Auf einem µC hat man eventuell auch gar nicht, wo stderr hingeht.
constexpr ist mit einem C23-fähigem Compiler zwar möglich, aber im
static_assert funktioniert das dann trotzdem nicht.
So wie ich das verstehe, geht es nur mit skalaren Objekten. Und dafür funktioniert es auch in C23. Aber trotzdem: auch wenn es für Arrays funktionieren würde, so fehlen noch die constexpr-Funktionen, um alle Array-Elemente prüfen zu können.
"Nachteil": es geht nur wenn Compiler/Linker nicht genutzte Funktionen herausoptimieren darf. Das macht aber in einer Library sowieso Sinn.
Getestet mit gcc 12.2.
Du müsstest mal prüfen, ob das auch beim Erzeugen einer Library funktioniert.
1
/*! Linker Assertion
2
@param cond Bedingung wird auf true geprüft. Bei false wird ein Linker Fehler erzeugt
3
@param msg Fehlermeldung in C-Identifier Form, z.B. dies_ist_ein_Text_als_Identifier
4
5
Vor C23 kennt C kein constexpr. static_assert() funktioniert bei "const" Variablen nicht.
6
Um trotzdem Bedingungen von const Variablen nutzen zu können, kann man dieses Makro nutzen.
7
Es ruft bei cond==false eine nicht existierende Funktion auf.
8
9
@note
10
* Muss innerhalb einer Funktion aufgerufen werden.
11
* Geht nur, wenn der Compiler nicht genutzte Funktionen löschen darf. Beispiel ``gcc -O1``.
12
Ohne Optimierung wird der Fehler immer immer geworfen.
13
14
*/
15
#define STATIC_ASSERT_ON_LINKTIME(cond, msg) do { \
So langsam erscheint da wieder die ursprüngliche Schönheit der Sprache
C.
Naja, wenn man versucht, mit irgendwelchen Tricksereien was zu machen, das die Sprache eigentlich nicht hergibt, dann wird das immer hässlich, nicht nur in C.
Wäre es für den TO denkbar die Lib als C++ Lib anzulegen, alles im C Syntax zu belassen und nur diesen zu prüfenden Teil im C++ Syntax zu schreiben? Wenn ich so weiter denke befürchte ich allerdings, dass das restliche C Programm mit der C++ Lib nichts anfangen kann. Dann müßte wahrscheinlich alles als C++ Programm neu angelegt werden. Schwieriger Fall.
Andere Idee.
Wenn man die Variablen für das Arrays mit #defines anlegt, kann man das mit dem Präprozessor abfangen. Der Gedanke wie
Muss man eben beim Schreiben höllisch aufpassen. Wenn man das schon rein optisch sauber anlegt hat das Auge auch die Chance irgendwelche Fehler schneller zu sehen.
Je nach Anwendung kann es auch sinnvoll sein den Anwender gar nicht das Array anlegen zu lassen, sondern es zu generieren aus einem anderen Format (CSV oder einfach in einer Scriptsprache definieren). Dann können Prüfungen beim Generieren gemacht werden. Das kann man problemlos in ein Makefile mit einbauen.
Naja, wenn man versucht, mit irgendwelchen Tricksereien was zu machen,
das die Sprache eigentlich nicht hergibt, dann wird das immer hässlich,
nicht nur in C.
Hässlich ist ein sehr schwammiges Qualitätsmerkmal. Was verstehst du darunter?
Aus meiner Sicht ist hässlich:
"Man versteht nicht was das Teil Macht." -->
Im Falle von STATIC_ASSERT_ON_LINKTIME ist eigentlich schon klar dass es sich um eine static_assert handelt, die zur Link time ausgeführt wird.
"Man verlässt sich auf ein Konstrukt, es versagt aber unerkannt." --> passiert hier nicht. Ist Optimierung aus, bricht das ganze ab.
"Fehlermeldungen sind nichtssagend." --> die Linkermeldung ist durch die Verwendung eines aussagekräftigen Identifiers gegeben. Liegt aber am Anwender.
"Die Dokumentation ist lausig und weist einen nicht auf Sonderfälle hin" --> Sonderfälle sind dokumentiert.
Ich finde dass Attribut hässlich passt hier nicht.
Naja, wenn man versucht, mit irgendwelchen Tricksereien was zu machen,
das die Sprache eigentlich nicht hergibt, dann wird das immer hässlich,
nicht nur in C.
Hässlich ist ein sehr schwammiges Qualitätsmerkmal. Was verstehst du
darunter?
...
Ich finde dass Attribut hässlich passt hier nicht.
Ich finde es ebenfalls Pott-hässlich ... und vollkommen nutzlos, solange Du nicht alle Array-Elemente prüfst.
Hatte ich ja oben schon gefragt: welche schönes Lösung schlägst Du vor?
Ich finde es ebenfalls Pott-hässlich ... und vollkommen nutzlos, solange
Du nicht alle Array-Elemente prüfst.
Hatte ich ja oben schon gefragt: welche schönes Lösung schlägst Du vor?
Nichts leichter als das:
1
/* Mikrocontroller.net
2
Diskussion: Nicht Kompilieren bei einem bestimmten Wert in einem const array
3
https://www.mikrocontroller.net/topic/557973#new
4
*/
5
6
/*! Linker Assertion
7
@param cond Bedingung wird auf true geprüft. Bei false wird ein Linker Fehler erzeugt
8
@param msg Fehlermeldung in C-Identifier Form, z.B. dies_ist_ein_Text_als_Identifier
9
10
Vor C23 kennt C kein constexpr. static_assert() funktioniert bei "const" Variablen nicht.
11
Um trotzdem Bedingungen von const Variablen nutzen zu können, kann man dieses Makro nutzen.
12
Es ruft bei cond==false eine nicht existierende Funktion auf.
13
14
@note
15
* Muss innerhalb einer Funktion aufgerufen werden.
16
* Geht nur, wenn der Compiler nicht genutzte Funktionen löschen darf. Beispiel ``gcc -O1``.
17
Ohne Optimierung wird der Fehler immer immer geworfen.
18
19
*/
20
#define STATIC_ASSERT_ON_LINKTIME(cond, msg) do { \
Die Schleife wird ab -O1 vollständig rausoptimiert.
Selbst C++ Templates kommen an diese Einfachheit so nicht ran.
Das kann jeder lesen der einigermaßen C kann.
Das ist in meinen Augen nicht "Pott-hässlich" sondern eher "Nice" 🙂
Das ist nur "Nice" für Leute, die nichts anderes als C kennen.
Der TO hat ein Problem in C und nicht in C++.
Ich programmiere alles Neue nur mit C++. Aber manchmal hat man halt C-Code, der eingesetzt wird. Und das Schöne ist: das funktioniert sogar in C++.
(Dort würde ich aber eher constexpr empfehlen.).
Och Leutens, warum prügelt ihr auf mich ein? Der Vorschlag kann für den TO eine Lösung in C sein, die (wenn -O1 möglich ist) recht einfach ist. Ich hab nie behauptet, dass ich C++ schlecht finde oder C bevorzuge…
Ich habe diese Technik in einem Projekt genutzt,
in dem hauptsächlich einzelne Werte und kleine Arrays geprüft wurden.
Dort hat die Optimierung immer funktioniert.
Daher ist das totaler Schwachsinn.
Nein, schwachsinnig wäre es, wenn die Assertion nicht erkannt ausfällt.
Das tut sie aber nicht. Wenn nicht optimniert werden kann,
meldet das die Assertion. Dann muss man halt andere Techniken nutzen.
Ich habe diese Technik in einem Projekt genutzt,
in dem hauptsächlich einzelne Werte und kleine Arrays geprüft wurden.
Dort hat die Optimierung immer funktioniert.
Tja: da hast Du einen Test für Deine Nicht-Lösung, der keine Aussagekraft besitzt.
Daher ist das totaler Schwachsinn.
Nein, schwachsinnig wäre es, wenn die Assertion nicht erkannt ausfällt.
Das tut sie aber nicht. Wenn nicht optimniert werden kann,
meldet das die Assertion. Dann muss man halt andere Techniken nutzen.
Nun gut, eine Nicht-Lösung, die anzeigt, dass sie keine Lösung ist, ist nicht ganz so schlimm wie eine Nicht-Lösung, die still versagt. Es bleibt: ein Nicht-Lösung.
Eine Ergänzung hab ich noch. Aber gilt nur für gcc (nutzt der TO).
Per "#pragma GCC unroll" zwingt man den Compiler, die nächste for Schleife komplett aufzurollen:
Eine Ergänzung hab ich noch. Aber gilt nur für gcc (nutzt der TO).
Per "#pragma GCC unroll" zwingt man den Compiler, die nächste for
Schleife komplett aufzurollen:
Na prima, und Du bist sicher, dass Du das für den Rest der TU möchtest? Und warum nur bist 1000? Also, es bleibt leider Mist.
Abhängig von der Optimierung
Abhängig vom pragma
Abhängig von der Array-Größe
1
>#pragmaGCCunroll1000
2
>for(size_ti=0;i<ARRAY_COUNT(test);i++){
3
>STATIC_ASSERT_ON_LINKTIME(test[i][1]<5,
4
>test_parameter_muss_kleiner_5_sein);
5
>}
6
>
Ich höhre jetzt aber auf, will Euch nicht zur Weisglut bringen.
Nein, schwachsinnig wäre es, wenn die Assertion nicht erkannt ausfällt.
Das tut sie aber nicht. Wenn nicht optimniert werden kann,
meldet das die Assertion.
Nein, es kommt beim Linken ein Fehler, der suggeriert, dass die Werte falsch seien, also eine vollkommen irreführende Fehlermeldung.
"Nachteil": es geht nur wenn Compiler/Linker nicht genutzte Funktionen
herausoptimieren darf. Das macht aber in einer Library sowieso Sinn.
Getestet mit gcc 12.2.
Du müsstest mal prüfen, ob das auch beim Erzeugen einer Library
funktioniert.
Das sieht perfekt aus und gefällt mir richtig gut! Funktioniert auch hier Prima und wie es gewollt ist. Vielen Dank!
"Nachteil": es geht nur wenn Compiler/Linker nicht genutzte Funktionen
herausoptimieren darf. Das macht aber in einer Library sowieso Sinn.
Getestet mit gcc 12.2.
Du müsstest mal prüfen, ob das auch beim Erzeugen einer Library
funktioniert.
Das sieht perfekt aus und gefällt mir richtig gut! Funktioniert auch
hier Prima und wie es gewollt ist. Vielen Dank!
In gcc (und auch g++) gibt es eine ähnliche Möglichkeit per Attribut "error" (oder wenn man will auch "warning"):
1
constinta[]={1,3};
2
3
__attribute__((__error__("some message")))
4
voidfun_error(void);
5
6
__attribute__((__optimize__("Os")))
7
voidtest_a1(void)
8
{
9
if(a[1]>2)
10
fun_error();
11
}
12
13
intmain()
14
{
15
return0;
16
}
In test_a1() wird der Test ausgeführt, und wenn gcc einen Aufruf für fun_error erzeugen müsste, gibt der Compiler stattdessen einen Fehler aus:
1
foo.c: In function 'test_a1':
2
foo.c:10:9: error: call to 'fun_error' declared with attribute error: some message
3
10 | fun_error();
4
| ^~~~~~~~~~~
Damit die Funktion bei erfüllter Assertion nicht getriggert wird wenn nicht optimiert wird, hab ich Attribute "optimize" angegeben.
Auch wenn das so funktioniert ist es nicht wirklich schön...
Immerhin eine weitere Möglichkeit in der Werkzeugkiste. Vorteil gegenüber der o.g. Methode ist, dass die Diagnostic direkt vom Compiler kommt und nicht vom Linker.
Damit Du auch ohne Optimierung arbeiten kannst, kann man alle Assertions in einer Funktion zusammenfassen. Dann wird nur diese Funktionn per attribut optimiert.
Dann entstehen auch keine irreführende Fehlermeldung, wie Rolf angemerkt hat.
Beispiel:
1
#include<stdio.h>
2
3
/*! Linker Assertion
4
@param cond Bedingung wird auf true geprüft. Bei false wird ein Linker Fehler erzeugt
5
@param msg Fehlermeldung in C-Identifier Form, z.B. dies_ist_ein_Text_als_Identifier
6
7
Vor C23 kennt C kein constexpr. static_assert() funktioniert bei "const" Variablen nicht.
8
Um trotzdem Bedingungen von const Variablen nutzen zu können, kann man dieses Makro nutzen.
9
Es ruft bei cond==false eine nicht existierende Funktion auf.
10
11
@note
12
* Muss innerhalb einer Funktion aufgerufen werden.
13
* Geht nur, wenn der Compiler nicht genutzte Funktionen löschen darf. Beispiel ``gcc -O1``.
14
Ohne Optimierung wird der Fehler immer immer geworfen.
15
16
*/
17
#define STATIC_ASSERT_ON_LINKTIME(cond, msg) do { \
Hier nochmal eine Variante über gcc attribut error.
Jetzt wird ein Call auf eine nicht vorhandene Funktion vermieden.
Das Assertion Makro Funktion habe ich umbenannt in STATIC_ASSERT_CONST.
Die Fehlermeldung kann jetzt auch als String genutzt werden, eine Schreibweise als Identifier ist nicht mehr notwendig.
Es gibt ja auch Leute, die gehen zu Dominas und lassen sich auspeitschen.
Und es gibt Leute, die verwenden C; Insbesondere in Kombination mit solchen Konstrukten.
Ich vermute es gibt eine große Schnittmenge.
Auch in diesem Fall empfehle ich den soften Umstieg auf C++, nur um die kleinen Goodies zu nutzen. Denn auch hier bin ich überzeugt, dass der Code des TO fast unmodifiziert sich als C++ übersetzen lässt.
Hier nochmal eine Variante über gcc attribut error.
Jetzt wird ein Call auf eine nicht vorhandene Funktion vermieden.
Das Assertion Makro Funktion habe ich umbenannt in STATIC_ASSERT_CONST.
Die Fehlermeldung kann jetzt auch als String genutzt werden, eine
Schreibweise als Identifier ist nicht mehr notwendig.
Statt dieses Ungetüms, was nun nach einigen Nachfragen und weiteren Versuchen endlich hoffentlich korrekt ist, würde ich in dem Fall eine Laufzeitassertion (!NDEBUG) vorziehen, und ggf. in den Build-Prozess einen Laufzeittest einbauen. Allemal besser als dieser ... Mist. Oder wie oben schon gesagt, die kleinen Goodies von C++ nutzen.