August 2021

Fehler in C-Code

Fast hoffnungslose Ursachen-Erkennung

Reguläre Ausdrücke (RA) sind vergleichsweise sehr mächtig und ein elegantes Mittel, um beispielsweise komplexe und genaue Prüfungen vorzunehmen, sofern die RA in ein geeignet konzipiertes Kommando eingebaut sind. Es liegt nahe, RA in Shell-Skripten vielfältig einzusetzen, wie nachfolgend gezeigt, mit dem Kommando expr.

WertePrep() { [ N -lt 2 ] && { Err "Keine Namen gesetzt"; return 1; } local cfg:.30 d=. g:09 b:.30 expr "${{1}}" :g '[><][^><]\{2,}$' || { Err "Format <>#a"; return 1; } let "g>28" && { Err "Format <>#a zu lang: $g"; return 1; } expr "${{1}}" :cfg '\([><][^><]\{2,}\)$' conv -d"${Tab}d " cfg expr "$cfg" :d '^\([><]\)' || { Err "Format ><"; return 1; } expr "$cfg" :g '^[><]\([0-9.]\{1,5}\)' || { Err "Format Gewichtung"; return 1; } expr "$cfg" :b '\([a-zA-Z_].\{0,}\)$' || { Err "Format Bezeichnung"; return 1; } if expr "$g" :: '\.' then expr "$g" :: '^\.' && g="0$g" expr "$g" :: '\.$' && g="${g}0" let "g<0.1||g>99.9" && { Err "Gewichtung '$g'"; return 1; } else [ g -lt 1 -o g -ge 100 ] && { Err "Gewichtung '$g'"; return 1; } g="$g.0" fi let "G+g>=100.01" && { Err "Gewichtung $((G+g))"; return 1; } let "G+=g" expr "${{1}}" :$1 '[><][^><]\{2,}$' = '' $2="$d $g $b" return 0 }

Vollständiger Skript-Code

RA sind Zeichenketten folgender Art: '[><][^><]\{2,}$'. RA werden vor Benutzung aufwendig zu einer anderen Form kompiliert. Erst die kompilierte Form wird auf den zu untersuchenden Text (hier: "${{1}}") angewandt. Das Ziel ist es, daß Text und RA zueinander passen (match).

Neuer Code

Es wurde ein Cache-System für RA und kompilierte RA implementiert, damit das aufwendige Kompilieren bei mehrfach verwendeten RA nach dem ersten Vorkommnis entfallen kann. Das ist besonders wirksam innerhalb von Schleifenkonstruktionen, die tausende Male einen gleichen RA heranziehen.
Nach der Implementierung zeigten sich jedoch immer mal wieder diffuse Fehlererscheinugen, deren Ursache brutto über ein Jahr lang nicht gefunden und daher auch nicht beseitigt werden konnte!

Fehlererscheinungen zeigten sich oft gar nicht, dann unvermutet subtil, bis hin zu krachender und unübersehbarer Auswirkung. Obwohl die verwendeten RA überall sich sehr ähnlich oder gar gleich waren. Dennoch war immer wieder keine klare Ursache greifbar! Das Verwenden des oben gezeigten Skriptes hat nun endlich zur Lösung geführt! Warum das so schwierig war, wird nachfolgend erzählt.

Verschleierungen

RA und zu untersuchender Text spielen zusammen. In Abhängigkeit davon können Fehler eine Auswirkung haben oder auch nicht. Details sind manchmal redundant, aber eben nicht immer. Bei Redundanz wirkt ein Fehler nicht, da das dadurch gestörte Merkmal - redundant ist.

Zu Fehlererscheinungen kam es erst, nachdem ein RA im Cache war und darin gefunden wurde, wodurch die Kompilier-Funktion nicht ausgeführt wurde. Beim jeweils ersten Anlaufen hatten die selben RA keine Fehler gezeigt!

Zu einem zweiten Antreffen kann es durch zwei oder mehr gleiche RA-Exemplare kommen oder durch mehrfaches zeitliches Aufsuchen ein und desselben Exemplars.

Ob ein vorhandenes Kommando expr überhaupt ausgeführt wird, ist nicht offensichtlich. Eine Ausführung ist oft bedingt.

Der Cache hat relativ wenige Speicherplätze. Es ist ein Ringspeicher, der alte Positionen überschreibt. Wenn ein alter gespeicherter RA überschrieben wird, kann er nicht mehr im Cache gefunden werden, und es kann daher nicht zu einem zugehörigen Nichtausführen der Kompilier-Funktion kommen! Andere RA, die (stattdessen) im Cache gefunden werden, können fehlerunempfindlich oder mit unterschiedlichem Grad fehlerunwahrscheinlich sein.

Ursache

Die Ursache der Fehlererscheinungen ist das Nichtausführen der Kompilier-Funktion. Diese Funktion füllt nicht sämtliche Informationen in den kompilierten RA, sondern legt wenige Informationen auch in globalen Variablen ab. Außerdem initialisiert sie einige Variablen, die in ihr nicht benutzt werden.
Es wurden bei der Entwicklung des RA-Cache neben dem Kompilat sogar betreffende globale Informations-Variablen berücksichtigt. Allerdings wurde eine davon und Initialisierungen nicht beachtet. Deshalb konnte ein sehr komplexes Fehlerbild entstehen, da alle Funktionen nach der Kompilier-Funktion ganz verschieden von der (ehemaligen) Hinterlassenschaft der Kompilier-Funktion betroffen sind, die ja bei Cache-Fund nicht aufgerufen wird.

Fazit

Diese BRE wurden 1997 entwickelt, die XRE 2015, stärker modularisiert und strukturiert (struct). Der alte BRE-Code ist konzeptionell gewiß nicht optimal.
Der neue Code, das Cache-System an sich, war und ist nicht fehlerhaft, was jedoch logischerweise immer wieder vermutet wurde, womit immer wieder der Holzweg beschritten wurde. Sehr geringfügige aber folgenschwere Übersehungen wurden beim Verweben des neuen Code mit dem alten BRE-System begangen.



Copyright © 2021 - Helmut Schellong

Fehler C-Code