Forum: Mikrocontroller und Digitale Elektronik Abfrage auf NULL


von Markus (Gast)


Lesenswert?

Hallo zusammen,

folgendes Beispiel in C. Hier main.c
1
#include "main.h"
2
3
4
static void TestFunction( TestStruct_T *t_TestVal )
5
{
6
  if( t_TestVal == NULL )
7
  {
8
    printf(" NULL");
9
  }
10
  else
11
  {
12
    printf(" nicht NULL");
13
  }
14
15
}
16
17
18
19
20
int main()
21
{
22
  TestStruct_T t_Test;
23
24
  TestFunction( &t_Test );
25
26
  return 0;
27
}

und noch main.h:
1
typedef struct{
2
3
  uint8_t u8_val;
4
} TestStruct_T;

Im Header wird einfach eine Struktur definiert, von der in main.c eine 
Variable erzeugt wird. Diese wird denn per Referenz an eine Funktion 
übergeben. In der Funktion wird die Variable auf NULL abgefragt. Macht 
solch eine Abfrage überhaupt Sinn?
Meiner Meinung nach kann ich mir die Abfrage sparen, da ja nichts 
dynamisch erzeugt wird. Oder sehe ich das falsch? Wenn ich mit Pointern 
arbeite, dann initialisiere ich diese immer auf NULL, dann kommt das 
alloc und danach frage ich immer auf NULL ab.
Oder macht die Abfrage im Beispiel doch Sinn?

Gruß Markus

von Clemens W. (daxmus)


Lesenswert?

TestFunction erwartet einen Pointer, keine Referenz. Daher macht die 
Abfrage prinzipiell Sinn.
In deinem konkreten Fall wäre sie wohl überflüssig, aber du kannst ja 
nicht garantieren, dass die Funktion nur an dieser einen Stelle und in 
dieser Konstellation aufgerufen wird.

von (prx) A. K. (prx)


Lesenswert?

In diesem Beispiel bringt die Abfrage nichts, weil der einzige Zugriff 
über t_TestVal die Abfrage ist. Das liegt aber am Beispiel.

Die Abfrage ist trotzdem sinnvoll, weil reale Programme durch reale 
Programmierer selten perfekt sind und irgendwann mal die TestFunction 
über ein paar Ecken herum einen Pointer verpasst bekommt, der NULL ist.

von Sven B. (scummos)


Lesenswert?

Markus schrieb:
> Meiner Meinung nach kann ich mir die Abfrage sparen, da ja nichts
> dynamisch erzeugt wird.
1
TestFunction(nullptr);

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Markus schrieb:
> Macht
> solch eine Abfrage überhaupt Sinn?

Kommt auf die Dokumentation von TestFunction an: Wenn die sagt, dass 
NULL kein gültiger Wert ist, dann hat da keiner NULL als Parameter zu 
übergeben. Da Fehler aber nun mal passieren, ist es sinnvoll, die 
nicht-nulli-ness des Parameters z.B. während der Entwicklungsphase zu 
testen. Dafür haben wir asserts:
1
static void TestFunction_tF( TestStruct_T *tpcx_TestVal )
2
{
3
    assert( tpcx_TestVal );
4
5
    printf(" nicht NULL");
6
}


> Oder macht die Abfrage im Beispiel doch Sinn?

Im konkreten Beispiel nicht. Aber in der Regel solltest Du Dir schon 
überlegen, was die Aufgabe einer Funktion ist, und ob einige der 
Parameter der Funktion optional sein sollen oder nicht.

Es gibt auch Kollegen, die Dir raten würden, den Parameter immer zu 
testen und ggf. einen Fehlercode zurück zu geben. Sprichwort: Defensives 
Programmieren. (Meiner Meinung nach, der direkte Weg in die 
Wartungs-Hölle).

> Gruß Markus

mfg Torsten

von (prx) A. K. (prx)


Lesenswert?

Torsten R. schrieb:
> Es gibt auch Kollegen, die Dir raten würden, den Parameter immer zu
> testen und ggf. einen Fehlercode zurück zu geben. Sprichwort: Defensives
> Programmieren. (Meiner Meinung nach, der direkte Weg in die
> Wartungs-Hölle).

Weshalb die Programmiersprache D sogenannte Contracts kennt:
https://dlang.org/spec/contracts.html

von Sven B. (scummos)


Lesenswert?

A. K. schrieb:
> Torsten R. schrieb:
>> Es gibt auch Kollegen, die Dir raten würden, den Parameter immer zu
>> testen und ggf. einen Fehlercode zurück zu geben. Sprichwort: Defensives
>> Programmieren. (Meiner Meinung nach, der direkte Weg in die
>> Wartungs-Hölle).

Für defensives Programmieren brauchst du m.E. den Fehlercode gar nicht. 
Gerade für GUI-Anwendungen ist ein if (!x) return; das genaue Gegenteil 
der Wartungshölle ...

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Sven B. schrieb:

> Für defensives Programmieren brauchst du m.E. den Fehlercode gar nicht.
> Gerade für GUI-Anwendungen ist ein if (!x) return; das genaue Gegenteil
> der Wartungshölle ...

Später kommt dann ein Anruf vom Kunden, der sagt: Ich hatte heute drei 
mal den Fall, dass ich Dialog X geöffnet habe und der sich sofort wieder 
geschlossen hat.

Soetwas eignet sich meiner Meinung nach hervorragend, um Fehler im Code 
möglist lange zu verbergen.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

A. K. schrieb:
> Torsten R. schrieb:
>> Es gibt auch Kollegen, die Dir raten würden, den Parameter immer zu
>> testen und ggf. einen Fehlercode zurück zu geben. Sprichwort: Defensives
>> Programmieren. (Meiner Meinung nach, der direkte Weg in die
>> Wartungs-Hölle).
>
> Weshalb die Programmiersprache D sogenannte Contracts kennt:
> https://dlang.org/spec/contracts.html

Der von Dir verlinkte Teil der Dokumentation passt aber eher zu meinem 
Vorschlag, asserts zu verwenden. Und Contracts ist nichts, dass von D 
erfunden wurde.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Torsten R. schrieb:
> Später kommt dann ein Anruf vom Kunden, der sagt: Ich hatte heute drei
> mal den Fall, dass ich Dialog X geöffnet habe und der sich sofort wieder
> geschlossen hat.

Und ein solches Verhalten, mitsamt klarer Aussage um Logfile, ist 
irgendwie schlimmer als ein Programmabsturz?

von Clemens W. (daxmus)


Lesenswert?

Torsten R. schrieb:
> Später kommt dann ein Anruf vom Kunden, der sagt: Ich hatte heute drei
> mal den Fall, dass ich Dialog X geöffnet habe und der sich sofort wieder
> geschlossen hat.

Unangenehm, ja. Noch unangenehmer ist aber, wenn der Kunde anruft und 
sagt, ihm sei das Programm jedes mal bei dem Versuch den Dialog zu 
öffnen abgestürzt.

Im Grunde ist es doch aber so: Ein Pointercheck ist so gut wie immer 
sinnvoll, das Fehlerhabdling danach ist aber stark programm-, bzw. 
kontextabhängig.

von (prx) A. K. (prx)


Lesenswert?

Torsten R. schrieb:
> Der von Dir verlinkte Teil der Dokumentation passt aber eher zu meinem
> Vorschlag, asserts zu verwenden.

Das war auch nur ein Hinweis, wie man das in modernerer Umgebung machen 
kann, um den Wildwuchs von Tests und eigentlichem Code in formale Bahnen 
zu lenken. Denn das ist ja das Problem bei systematischen Tests: Wenn 
man nicht sehr viel Disziplin walten lässt, sieht man irgendwann im Code 
den Wald vor lauter Bäumen nicht mehr.

> Und Contracts ist nichts, dass von D erfunden wurde.

Wie man unschwer an der "Reading List" erkennen kann, die darin verlinkt 
ist.

: Bearbeitet durch User
von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

A. K. schrieb:
> Torsten R. schrieb:
>> Später kommt dann ein Anruf vom Kunden, der sagt: Ich hatte heute drei
>> mal den Fall, dass ich Dialog X geöffnet habe und der sich sofort wieder
>> geschlossen hat.
>
> Und ein solches Verhalten, mitsamt klarer Aussage um Logfile, ist
> irgendwie schlimmer als ein Programmabsturz?

1) Stehen die Chancen ganz gut, dass der Fehler garnicht an den Kunden 
ausgeliefert wurde, weil er bereits wärend der Entwicklung gefunden 
wurde.

2) Kann man ggf. asserts auch im Code belassen, der an den Kunden geht. 
Dann sürzt das Program zwar ab, liefert aber wertvolle Informationen, um 
den Fehler zu finden und zu beseitigen.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Clemens W. schrieb:

> Im Grunde ist es doch aber so: Ein Pointercheck ist so gut wie immer
> sinnvoll, das Fehlerhabdling danach ist aber stark programm-, bzw.
> kontextabhängig.

Wenn die Funktion dokumentiert, dass der Parameter nicht NULL zu sein 
hat und der einzige Grund, warum die Funktion einen Fehler-Code zurück 
liefert, dieser Test auf NULL ist, dann erweiterst Du die Komplexität 
unnötig. Und dass ggf. für einen Fall, der in fehlerfreier Software 
nicht auftauchen "dürfte". Wenn die Dokumentation der Funktion nun auch 
noch dokumentiert, dass der einzige Fall, in dem die Funktion einen 
Fehler meldet genau der Fall ist, dass der Aufrufer einen nicht 
korrekten Parameter übergeben hat. Was soll der Aufrufer mit dem 
Rückgabewert machen? :
1
TestStruct_T t_Test;
2
3
error_t rc = TestFunction( &t_Test );
4
assert( rc != ec_wrong_argument );
5
(void)rc;

Die Funktion kann natürlich versuchen, irgend etwas "Sinnvolles" zu 
machen (aka: Shit in -> Shit out), aber wie Du ja selber schreibst, ist 
das stark kontextabhängig und den Kontext legt in der Regel der Aufrufer 
fest.

Und für (echte) Fehlerfälle, brauchen wir häufig Funktionen, die selber 
keine Fehler erzeugen können (rollback). Hier aus Funktionen, die keine 
Fehler erzeugen welche zu machen, die aus SW-Bugs, Fehler erzeugen, ist 
da nicht hilfreich.

von Mikro 7. (mikro77)


Lesenswert?

Torsten R. schrieb:
> Es gibt auch Kollegen, die Dir raten würden, den Parameter immer zu
> testen und ggf. einen Fehlercode zurück zu geben.

Das führt direkt in die Hölle. [Full Ack]

Gerade letztens musste ich wieder durch solch einen "Müll". Jeder 
Funktion gibt fein säuberlich ihren Error Code zurück (für extrem 
unwahrscheinliche Fehler oder interne Inkonsistenzen) und die 
Problematik wird auf den Aufrufer verschoben (der kaum Infos hat um das 
Problem sinnvoll zu behandeln).

Das ist doch echt ein Witz: Aufrufer übergibt einen Null Pointer und 
bekommt den Error Code: Null Pointer, und macht jetzt was? Er gibt 
seinen Aufrufer einen generischen Error Code zurück. Und was macht der 
jetzt?

Ein guter Kompromiss ist assert(). Das Problem sollte beim Test 
auflaufen. Im Produktionscode kann man dann mit NDEBUG übersetzen (wenn 
man denn will).

Imho besser (aber auch aufwändiger und unübersichtlicher -- was wieder 
kontraproduktiv sein kann) ist das Kapseln des Skalars in einer struct 
(bspw. als typisierte non-NULL Pointer). Da kann der Compiler das 
Problem schon vorneweg sehen. Das "lohnt" nur in umfangreichen 
Projekten.

Bei (externen) Interfaces... ok, ja, da kann und sollte man wohl Error 
Codes zurück geben.

von (prx) A. K. (prx)


Lesenswert?

Torsten R. schrieb:
> 1) Stehen die Chancen ganz gut, dass der Fehler garnicht an den Kunden
> ausgeliefert wurde, weil er bereits wärend der Entwicklung gefunden
> wurde.

Schon mal das Tomcat-Logfile (catalina.out) einer ganz normalen beim 
Kunden installierten Tomcat/Java-Anwendung gesehen? ;-)

von Clemens W. (daxmus)


Lesenswert?

Torsten R. schrieb:
> Wenn die Funktion dokumentiert, dass der Parameter nicht NULL zu sein
> hat und der einzige Grund, warum die Funktion einen Fehler-Code zurück
> liefert, dieser Test auf NULL ist, dann erweiterst Du die Komplexität
> unnötig. Und dass ggf. für einen Fall, der in fehlerfreier Software
> nicht auftauchen "dürfte". Wenn die Dokumentation der Funktion nun auch
> noch dokumentiert, dass der einzige Fall, in dem die Funktion einen
> Fehler meldet genau der Fall ist, dass der Aufrufer einen nicht
> korrekten Parameter übergeben hat. Was soll der Aufrufer mit dem
> Rückgabewert machen?

Da hast Du natürlich recht, ein Fehler "nullptr übergeben" von einer 
Funktion, die keinen nullptr erwartet ist irgendwie sinnlos. Dass die 
Funktion intern diesen Fall aber dennoch handelt, halte ich für guten 
Programmierstil. Ein Absturz wird verhindert und in diesem Fall ist der 
Aufrufer einfach selbst schuld.

V.a. da Dokumentation und Programmierung teilweise zwei Paar Schuhe 
sind. Selbst in der Qt Doku (die m.M. nach sehr gut und umfangreich ist) 
finden sich immer mal wieder Fehler (auf die auch auch schon 
reingefallen bin).

von Sven B. (scummos)


Lesenswert?

He, defensive Programmierung ist nicht "jede Funktion gibt einen 
Fehlercode zurück". Das ist einfach völlig die falsche Interpretation 
des Begriffs. Defensive Programmierung ist "ich versuche mal in den 
Fällen wo das noch irgendwie geht irgendwas zu machen was nicht allzu 
schlimm ist". Natürlich kann deine Funktion, die eine Datei öffnet ein 
assert() machen wenn die Datei nicht vorhanden ist, weil das hätte ja 
wasweißichwer vorher schon prüfen sollen. Sie kann aber auch einfach ein 
return machen und eine Warnung in's Log schreiben. Das ist in den 
meisten Fällen einfach das bessere Verhalten.

: Bearbeitet durch User
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.