Forum: Compiler & IDEs Nicht Kompilieren bei einem bestimmten Wert in einem const array


von E. K. (nanostudent)


Lesenswert?

Hi,

ich möchte folgendes realisieren:
1
#include <stdio.h>
2
3
const int test[][4] = { {1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4} };
4
5
static_assert(test[0][1] < 5, "Fehler");
6
static_assert(test[1][1] < 5, "Fehler");
7
static_assert(test[2][1] < 5, "Fehler");
8
static_assert(test[3][1] < 5, "Fehler");
9
10
11
int main()
12
{
13
    printf("%d", test[0][1]);
14
15
    return 0;
16
}

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...

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

constexpr int test[][4] =

Oliver

von Wilhelm M. (wimalopaan)


Lesenswert?

Oliver S. schrieb:
> constexpr int test[][4] =

Vermute allerdings, dass es C meint. Sonst hätte er sicher std::array<> 
und <cstdio> geschrieben.

von E. K. (nanostudent)


Lesenswert?

Ah Mist, vergessen noch zu Erwähnen welche Umgebung es geht usw.
Also es ist leider nur reines C. Der GCC Compiler vom Microchip Studio.

Da ist ja leider C++ bzw constexpr nicht möglich.

So wie Wilhelm schreibt.

Als neuer Nutzer kann ich leider nur iwie alle 30 Minuten was 
schreiben...

von Oliver S. (oliverso)


Lesenswert?

Eske schrieb:
> Da ist ja leider C++ bzw constexpr nicht möglich.

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.

Oliver

von Εrnst B. (ernst)


Lesenswert?

Eske schrieb:
> 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.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

so sollte das im Grunde funktionieren. Allerdings werden Fehler nur auf 
der stderr ausgeben. Einen Compilerabbruch wie bei C++ gibt es nicht.
1
#include <stdio.h>
2
#include <assert.h>
3
4
const int test[][4] = {
5
     {1,2,3,4},
6
     {1,2,3,4},
7
     {1,2,3,4},
8
     {1,2,3,4}
9
};
10
    
11
    
12
int main()
13
{
14
    assert( test[0][1] < 5 );  
15
    printf("%d", test[0][1]);
16
    return 0;
17
}

Edit:
Oder soll es doch C++ sein?

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Eske schrieb:
> Die static_assert funktionieren so aber nicht.

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.

Eske schrieb:
> Ah Mist, vergessen noch zu Erwähnen welche Umgebung es geht usw.
> Also es ist leider nur reines C. Der GCC Compiler vom Microchip Studio.
>
> Da ist ja leider C++ bzw constexpr nicht möglich.

Ist da kein g++ dabei?

> Als neuer Nutzer kann ich leider nur iwie alle 30 Minuten was
> schreiben...

Da darfst du dich vermutlich beim Haustroll bedanken, der sonst das 
Forum mit endlosen Kopien seiner Ergüsse fluten würde.

Veit D. schrieb:
> 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.

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Allerdings werden Fehler nur auf
> der stderr ausgeben. Einen Compilerabbruch wie bei C++ gibt es nicht.

Und deswegen trifft es nicht die Anforderungen des TO (s.o.).

von Wilhelm M. (wimalopaan)


Lesenswert?

Oliver S. schrieb:
> 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.

von Klaus H. (klummel69)


Lesenswert?

So etwas geht über eine "Linker Assertion".

"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 {         \
16
  extern void assert_by_nonexisting_function__##msg();     \
17
  if( !(cond)) {assert_by_nonexisting_function__##msg();}  \
18
} while(0)
19
20
#include <stdio.h>
21
22
const int test[][4] = {{1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4} };
23
const int testvar = 256;
24
25
int main()
26
{
27
  // error: undefined reference to `assert_by_nonexisting_function_test_parameter_muss_kleiner_5_sein'
28
  STATIC_ASSERT_ON_LINKTIME(test[0][1] < 5, test_parameter_muss_kleiner_5_sein);
29
  STATIC_ASSERT_ON_LINKTIME((testvar % 4 == 0), testvar_muss_vielfaches_von_4_sein);
30
31
    printf("%d", test[0][1]);
32
    return 0;
33
}

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

So langsam erscheint da wieder die ursprüngliche Schönheit der Sprache 
C. Damit geht alles, wenn sich nur ordentlich bemüht.

Oliver

von Wilhelm M. (wimalopaan)


Lesenswert?

Und ich bin jetzt gespannt auf die Iteration über alle Array-Elemente.

von MaWin O. (mawin_original)


Lesenswert?

Oliver S. schrieb:
> So langsam erscheint da wieder die ursprüngliche Schönheit der Sprache
> C.

Hahahahaha. Ja. Sehr schön.
Danke für den Lacher.

von Rolf M. (rmagnus)


Lesenswert?

Oliver S. schrieb:
> 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.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich denke einmal laut für alle vor mich hin.  :-)

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
1
#include <stdio.h>
2
3
#define Y0X0 1
4
#define Y0X1 2
5
#define Y0X2 3
6
#define Y0X3 4
7
8
#define Y1X0 1
9
#define Y1X1 2
10
#define Y1X2 3
11
#define Y1X3 4
12
13
#define MAX_XY 3
14
15
const int test[][4] = {
16
     {Y0X0,Y0X1,Y0X2,Y0X3},
17
     {Y1X0,Y1X1,Y1X2,Y1X3}
18
};
19
    
20
#if ( (Y0X0>MAX_XY) || (Y0X1>MAX_XY) || (Y0X2>MAX_XY) || (Y0X3>MAX_XY) ) 
21
    #error Fehler, Y0X? zu gross
22
#endif
23
 
24
#if ( (Y1X0>MAX_XY) || (Y1X1>MAX_XY) || (Y1X2>MAX_XY) || (Y1X3>MAX_XY) )
25
    #error Fehler, Y1X? zu gross
26
#endif
27
  
28
int main()
29
{
30
    printf("%d", test[0][1]);
31
    return 0;
32
}

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.

von MaWin O. (mawin_original)


Lesenswert?

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.

von Klaus H. (klummel69)


Lesenswert?

Rolf M. schrieb:
> 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.

von Wilhelm M. (wimalopaan)


Lesenswert?

Klaus H. schrieb:
> Rolf M. schrieb:
>> 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?

von Klaus H. (klummel69)


Lesenswert?

Wilhelm M. schrieb:
> 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 {         \
21
  extern void assert_by_nonexisting_function__##msg();     \
22
  if( !(cond)) {assert_by_nonexisting_function__##msg();}  \
23
} while(0)
24
25
#include <stdio.h>
26
27
const int test[][4] = {{1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4} };
28
29
30
#define ARRAY_COUNT(a) (sizeof(a)/(sizeof(a[0])))
31
32
int main()
33
{
34
  // Assertions: Parameter 2 jedes Datendsatzes muss kleiner 5 sein
35
  // Schleife wird durch -O1 vollständig raus optimiert.
36
  for(size_t i=0; i<ARRAY_COUNT(test); i++) {
37
    STATIC_ASSERT_ON_LINKTIME(test[i][1] < 5, test_parameter_muss_kleiner_5_sein);    
38
  }
39
40
  printf("%d", test[0][1]);
41
  return 0;
42
}

Du kannst es selber ausprobieren: https://godbolt.org/z/ahT787859

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" 🙂

von MaWin O. (mawin_original)


Lesenswert?

Klaus H. schrieb:
> 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.

von Klaus H. (klummel69)


Lesenswert?

MaWin O. schrieb:
> 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.).

von Wilhelm M. (wimalopaan)


Lesenswert?

Klaus H. schrieb:
>
> Du kannst es selber ausprobieren: https://godbolt.org/z/ahT787859
>
> Die Schleife wird ab -O1 vollständig rausoptimiert.

Und so einen Schwachsinn, der von den Optimierungsoptionen abhängt, 
willst Du uns als Lösung verkaufen?

> Selbst C++ Templates kommen an diese Einfachheit so nicht ran.

Braucht man nicht: geht ganz einfach mit Immediate-Functions:
1
constexpr int test[4][4] = { {1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4} };
2
3
static_assert([]{
4
    for(const auto& v : test) {
5
        for(const auto& e : v) {
6
            if (e > 4) return false;
7
        }
8
    }
9
    return true;
10
}(), "wrong value in array test");

> Das kann jeder lesen der einigermaßen C kann.

Glaube ich nicht.
Viel schlimmer ist allerdings, dass Deine Nicht-Lösung bei größeren 
Arrays nicht mehr funktioniert.

von Wilhelm M. (wimalopaan)


Lesenswert?

Klaus H. schrieb:
> 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++.

Aber so einen Blödsinn würde man in C++ nicht schreiben, weil man es 
nicht braucht.


> (Dort würde ich aber eher constexpr empfehlen.).

Blitzmerker: steht ganz oben.

von Rolf M. (rmagnus)


Lesenswert?

Klaus H. schrieb:
> Die Schleife wird ab -O1 vollständig rausoptimiert.

Das ist allerdings stark compiler- und linkerspezifisch. Und mit -Og 
bleibt sie drin.

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Klaus H. schrieb:
>> Die Schleife wird ab -O1 vollständig rausoptimiert.
>
> Das ist allerdings stark compiler- und linkerspezifisch. Und mit -Og
> bleibt sie drin.

Auch mit -O3 bleibt sie drin, wenn das Array länger ist (s.o.).

von Klaus H. (klummel69)


Lesenswert?

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…

Rolf M. schrieb:
> Das ist allerdings stark compiler- und linkerspezifisch. Und mit -Og
> bleibt sie drin.

Jepp, hab ich oben geschrieben.

von Wilhelm M. (wimalopaan)


Lesenswert?

Klaus H. schrieb:
> 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.

Nein ist er nicht, weil er einfach bei längeren Arrays nicht 
funktioniert!

So geht es selbst bei -O3 nicht mehr. Daher ist das totaler Schwachsinn.
1
const int test[][4] = {{1,2,3,4},
2
{1,2,3,4},
3
{1,2,3,4},
4
{1,2,3,4},
5
{1,2,3,4},
6
{1,2,3,4},
7
{1,2,3,4},
8
{1,2,3,4},
9
{1,2,3,4},
10
{1,2,3,4},
11
{1,2,3,4},
12
{1,2,3,4},
13
{1,2,3,4},
14
{1,2,3,4},
15
{1,2,3,4},
16
{1,2,3,4},
17
{1,2,3,4},
18
{1,2,3,4} 
19
};

von Klaus H. (klummel69)


Lesenswert?

Wilhelm M. schrieb:
> So geht es selbst bei -O3 nicht mehr.

Das ist ein Argument.

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

Klaus H. schrieb:
> Wilhelm M. schrieb:
>> So geht es selbst bei -O3 nicht mehr.
>
> Das ist ein Argument.
>
> 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.

von Klaus H. (klummel69)


Lesenswert?

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:
1
  #pragma GCC unroll 1000
2
  for(size_t i=0; i<ARRAY_COUNT(test); i++) {
3
    STATIC_ASSERT_ON_LINKTIME(test[i][1] < 5, test_parameter_muss_kleiner_5_sein);    
4
  }

Ich höhre jetzt aber auf, will Euch nicht zur Weisglut bringen.

Schönen Abend!

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Klaus H. schrieb:
> 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
>   #pragma GCC unroll 1000
2
>   for(size_t i=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.

Höhre Weisglut ;-)

von Klaus H. (klummel69)


Lesenswert?

1
  STATIC_ASSERT_ON_LINKTIME(ARRAY_COUNT(test) < 65534, nachfolgende_assertion_geht_nur_fuer_arrays_kleiner_65534);    
2
  #pragma GCC unroll 65534
3
  for(size_t i=0; i<ARRAY_COUNT(test); i++) {
4
    STATIC_ASSERT_ON_LINKTIME(test[i][1] < 5, test_parameter_muss_kleiner_5_sein);    
5
  }

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Klaus H. schrieb:
>> 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.

Nein, es kommt beim Linken ein Fehler, der suggeriert, dass die Werte 
falsch seien, also eine vollkommen irreführende Fehlermeldung.

: Bearbeitet durch User
von E. K. (nanostudent)


Lesenswert?

Erstmal vielen Dank an alle! Hab auch was dazu gelernt!

Klaus H. schrieb:
> So etwas geht über eine "Linker Assertion".
>
> "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!

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Eske schrieb:
> Erstmal vielen Dank an alle! Hab auch was dazu gelernt!
>
> Klaus H. schrieb:
>> So etwas geht über eine "Linker Assertion".
>>
>> "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
const int a[] = { 1, 3 };
2
3
__attribute__((__error__("some message")))
4
void fun_error (void);
5
6
__attribute__((__optimize__("Os")))
7
void test_a1 (void)
8
{
9
    if (a[1] > 2)
10
        fun_error();
11
}
12
13
int main ()
14
{
15
    return 0;
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.

von Klaus H. (klummel69)


Lesenswert?

Eine Ergänzung noch:

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 {         \
18
  extern void assert_by_nonexisting_function__##msg();     \
19
  if( !(cond)) {assert_by_nonexisting_function__##msg();}  \
20
} while(0)
21
22
#define ARRAY_COUNT(a) (sizeof(a)/(sizeof(a[0])))
23
24
const int test[][4] = {{1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4},{1,222,3,4},{1,2,3,4},
25
{1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4}};
26
27
/* Const Assertions Wrapper
28
  Alle STATIC_ASSERT_ON_LINKTIME() Aufrufe sollten in dieser Funktion gekapselt werden.
29
  Durch den Optimierungslevel der Funktion ist sichergestellt, dass ohne globale
30
  Optimnierung keine ungewollten Assertions auftreten.
31
32
  @note Limitationen:
33
  - nur mit gcc nutzbar
34
  - for schleifen sind auf 65534 iterationen begrenzt.
35
*/
36
__attribute__ ((optimize ("-O3"))) void static_assert_checks()
37
{
38
  STATIC_ASSERT_ON_LINKTIME(ARRAY_COUNT(test) < 65535, nachfolgende_assertion_geht_nur_fuer_arrays_kleiner_65535);    
39
  #pragma GCC unroll 65534
40
  // Assertions: Parameter 2 jedes Datendsatzes muss kleiner 5 sein
41
  for(size_t i=0; i<ARRAY_COUNT(test); i++) {
42
    STATIC_ASSERT_ON_LINKTIME(test[i][1] < 5, test_parameter_muss_kleiner_5_sein);
43
  }
44
}  
45
46
int main()
47
{
48
  static_assert_checks();
49
  printf("%d", test[0][1]);
50
  return 0;
51
}

von Klaus H. (klummel69)


Lesenswert?

Johann L. schrieb:
> In gcc (und auch g++) gibt es eine ähnliche Möglichkeit per Attribut
> "error" (oder wenn man will auch "warning"):

Stimmt, daran hatte ich nicht gedacht. Ursprünglich nutzte ich diese 
Linker Assertions auf einem nicht gcc.

von Markus F. (mfro)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
> so sollte das im Grunde funktionieren. Allerdings werden Fehler nur auf
> der stderr ausgeben. Einen Compilerabbruch wie bei C++ gibt es nicht.
>
>
1
> #include <stdio.h>
2
> #include <assert.h>
3
> 
4
> const int test[][4] = {
5
>      {1,2,3,4},
6
>      {1,2,3,4},
7
>      {1,2,3,4},
8
>      {1,2,3,4}
9
> };
10
> 
11
> 
12
> int main()
13
> {
14
>     assert( test[0][1] < 5 );
15
>     printf("%d", test[0][1]);
16
>     return 0;
17
> }
18
>
>

Das lässt sich mit ein wenig inline-Assembler tatsächlich noch 
hinbetrügen (ich behaupte nicht, dass das schön ist):

#include <stdio.h>
const int test[][4] = {
     {1,2,3,4},
     {1,2,3,4},
     {1,2,3,4},
     {1,2,3,4}
};


int main()
{
    _asm_ __volatile__(
        ".if %c0 > 4\n\t"
        ".err           \n\t"
        ".endif \n\t"
            :: "i" (test[0][1]):
    );
    printf("%d", test[0][1]);
    return 0;
}

von Klaus H. (klummel69)


Lesenswert?

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.
1
#include <stdio.h>
2
3
4
/*! Erzwingt unter gcc eine Fehlermeldung */
5
__attribute__((__error__("STATIC_ASSERT_CONST() Error"))) void static_assert_const_error(void);
6
7
/*! Assertions for const
8
  @param cond Bedingung wird auf true geprüft. Bei false wird ein Error erzeugt
9
  @param msg  Fehlermeldung 
10
11
  Vor C23 kennt C kein constexpr. static_assert() funktioniert bei "const" Variablen nicht.
12
  Um trotzdem Bedingungen von const Variablen nutzen zu können, kann man dieses Makro nutzen.
13
  Bei cond==false erzeugt gcc eine Fehlermeldung mittels call auf static_assert_const_error().
14
15
  @note 
16
  * Muss innerhalb einer Funktion aufgerufen werden.
17
  * Geht nur mit gcc
18
  * Aufrufe sollten in einer Funktion mit aktivierter Optimierung gekapselt werden. 
19
  * Siehe static_assert_checks
20
*/
21
#define STATIC_ASSERT_CONST(cond, msg) do {if( !(cond)) {static_assert_const_error();}} while(0)
22
23
#define ARRAY_COUNT(a) (sizeof(a)/(sizeof(a[0])))
24
25
const int test[][4] = {{1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4},{1,1,3,4},{1,2,3,4},
26
{1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4}};
27
28
29
/* Const Assertions Wrapper
30
  Alle STATIC_ASSERT_CONST() Aufrufe sollten in dieser Funktion gekapselt werden.
31
  Durch den Optimierungslevel der Funktion ist sichergestellt, dass ohne globale
32
  Optimnierung keine ungewollten Assertions auftreten.
33
34
  @note Limitationen:
35
  - nur mit gcc nutzbar
36
  - for schleifen sind auf 65534 iterationen begrenzt.
37
*/
38
__attribute__ ((optimize ("-Os"))) void static_assert_checks()
39
{
40
  STATIC_ASSERT_CONST(ARRAY_COUNT(test) < 65535, "Nachfolgende assertion geht nur fuer arrays kleiner 65535");
41
  #pragma GCC unroll 65534
42
  // Assertions: Parameter 2 jedes Datendsatzes muss kleiner 5 sein
43
  for(size_t i=0; i<ARRAY_COUNT(test); i++) {
44
    STATIC_ASSERT_CONST(test[i][1] < 5, "Test Parameter muss kleiner 5 sein");
45
  }
46
}  
47
48
int main()
49
{
50
  static_assert_checks();
51
  printf("%d", test[0][1]);
52
  return 0;
53
}

von MaWin O. (mawin_original)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

Na, zum Glück ist der TO ja noch Student, und er wird seinen Code ggf. 
durch ein Review bringen müssen ... oder seinem Tutor zeigen müssen.

Vielleicht sollte man ja doch mal über C++ nachdenken. Hier nochmal das 
C++ Ungetüm:
Beitrag "Re: Nicht Kompilieren bei einem bestimmten Wert in einem const array"

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.

Klaus H. schrieb:
> 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.

von Markus F. (mfro)


Lesenswert?

... nicht schön, aber so geht's:
1
#define TABLE(A, B,C, D, E, F, G, H, I, J, K, L, M, N, O, P) \
2
    static_assert((A) < 5, "Fehler"); \
3
    static_assert((B) < 5, "Fehler"); \
4
    /* ... */                                    \
5
    const int test[][4] = {{(A), (B), (C), (D)}, \
6
                           {(E), (F), (G), (H)}, \
7
                           {(I), (J), (K), (L)}, \
8
                           {(M), (N), (O), (P)}}; 
9
10
TABLE(5, 2, 3, 4, \
11
      1, 2, 3, 4, \
12
      1, 2, 3, 4, \
13
      1, 2, 3, 4)
14
15
int main()
16
{
17
}

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.