fscanf() ändert Pointer? Wo liegt der Fehler?

Gast #3655496
Lesenswert?

Hallo Forum,
folgenden Code benutze ich unter Embedded Linux (Archlinux ARM).
Bis zum fscanf geht alles wunderbar. In Target2 steht der Pfad zum File 
drin.
Sobald fscanf() aufgerufen wurde stimmt der Zeiger auf Target2 nicht 
mehr und das Programm stürzt bei free() ab.
Target2 pointer: vorher 0x00016b90 nachher 0x0001000

fscanf() liest aber den korrekten Wert aus und liefert wie erwartet 1 
zurück.

Ist es ein Bug in fscanf() oder habe ich da irgendwas überlesen?
1
int_fast8_t get_value_gpio(char const * const gpionum)
2
{
3
  FILE* TMP2 = NULL;
4
  char * Target = build_target ("/sys/class/gpio/gpio", gpionum);
5
  char * Target2 = build_target (Target,"/value");
6
  int8_t ret = 0;
7
  int8_t val = 0;
8
  TMP2 = fopen (Target2, "r");
9
  if (TMP2)
10
      {
11
        ret = fscanf (TMP2, "%i", &val);
12
        fclose (TMP2);
13
        if (!ret)
14
    {
15
      printf ("Cant read correct value!");
16
    TMP2 = NULL;
17
    free (Target);
18
    Target = NULL;
19
    free (Target2);
20
    Target2 = NULL;
21
      return (-2);
22
    }
23
    TMP2 = NULL;
24
    free (Target);
25
    Target = NULL;
26
    free (Target2);
27
    Target2 = NULL;
28
        return(val);
29
      }
30
        else
31
          {
32
          printf ("Cant get value File doesnt exist!");
33
      free (Target);
34
      Target = NULL;
35
      free (Target2);
36
      Target2 = NULL;
37
            return (-1);
38
          }
39

40
}
#3655501
Lesenswert?

fscanfFehler schrieb:

Du hast einen Datentypfehler.

>         ret = fscanf (TMP2, "%i", &val);

fscanf muss durch das %i davon ausgehen, dass sich hinter der Adresse, 
die du ihm gibst (&val) sich ein int verbirgt.

Das tut es aber nicht
1
int8_t val = 0;

auf deinem System ist höchst wahrscheinlich ein int8_t nicht dasselbe 
wie ein int.

Damit schreibt dir fscanf beginnend mit der angegeben Adresse etwas in 
den Speicher, was einem int entspricht. Nur: der reservierte Platz 
reicht gar nicht dafür. D.h. fscanf überschreibt dir irgendwas im 
Speicher, was hinter dieser Variablen im Speicher liegt.

Glücklicherweise war das ein Pointer, so dass du einen Fehler beim 
free() bekommen hast. Dort hätte ja auch die Variable liegen können, die 
steuert ob die Herz-Lungen-Maschine noch weiter laufen soll oder nicht, 
und die im weiteren Programmlauf damit abgeschaltet würde. Ein Fehler 
den du nicht sofort bemerkt hättest.
Gast #3655514
Lesenswert?

Achja in Assembler wird folgendes gemacht:

if (TMP2)
00009a52:   ldr r3, [r7, #20]
00009a54:   cmp r3, #0
00009a56:   beq.n 0x9a9a <get_value_gpio+118>
286               ret = fscanf (TMP2, "%i", &val);
00009a58:   add.w r3, r7, #14
00009a5c:   ldr r0, [r7, #20]
00009a5e:   movw r1, #51424 ; 0xc8e0
00009a62:   movt r1, #0
00009a66:   mov r2, r3
00009a68:   blx 0x8bfc <__isoc99_fscanf>
00009a6c:   mov r3, r0
00009a6e:   strb r3, [r7, #15]
287               fclose (TMP2);
00009a70:   ldr r0, [r7, #20]
00009a72:   blx 0x8c14 <fclose>
#3655529
Lesenswert?

fscanfFehler schrieb:
> Es ist definiert:
>
> typedef signed char    int8_t;

Schön.
INteressiert nur fscanf nicht.

GIbst du %i an, dann musst du die Adresse eines int an fscanf übergeben. 
Punkt.

fscanf hat nur den Format-String um zu ergünden, was du da wohl für 
Adressen angeben hast und wieviele Bytes daher dort beschrieben werden 
müssen. Zwischen den %-Angaben im Format-String und den tatsächlichen 
Argumenten muss es daher eine 100% Übereinstimmung geben. Jede %-Angabe 
erfordert einen ganz bestimmten Datentyp (und nur diesen) in der 
Argumentliste. Stimmen die nicht zusammen, dann macht es im besten Fall 
'Peng'. Im schlimmsten Fall macht dein Programm Unsinn, der zunächst 
unentdeckt bleibt.
Gast #3655539
Lesenswert?

Würde es was ausmachen wenn ich
 int val;
dann fscanf()...

und dann
return((int_fast8_t)val);

mache?

Val kann ja wie gesagt nur 1 oder 0 enthalten. Daher müsste doch der 
cast gehen?

Habe soweit möglich fast immer die gerade passende int größe verwendet..
Damit alles möglichst klein gehalten wird. int wäre auf dem System 
32bit.
So hab ich bei allen rückgabewerten z.B.
int_fast8_t verwendet was signed char entspricht. Da die Rückgabewerte 
meist nur -4 bis 4 sind..
#3655544
Lesenswert?

fscanfFehler schrieb:
> Würde es was ausmachen wenn ich
>  int val;
> dann fscanf()...
>
> und dann
> return((int_fast8_t)val);
>
> mache?

Das ist ok.


Dein Problem ist ja folgendes:

fscanf hat nur den Format-String um zu ergünden, was du da wohl für 
Adressen angeben hast und wieviele Bytes daher dort beschrieben werden 
müssen. Zwischen den %-Angaben im Format-String und den tatsächlichen 
Argumenten muss es daher eine 100% Übereinstimmung geben. Jede %-Angabe 
erfordert einen ganz bestimmten Datentyp (und nur diesen) in der 
Argumentliste. Stimmen die nicht zusammen, dann macht es im besten Fall 
'Peng'. Im schlimmsten Fall macht dein Programm Unsinn, der zunächst 
unentdeckt bleibt.


Dein Problem steckt also in Annahmen, die fscanf machen muss, basierend 
auf dem Format String. fscanf hat keine andere Möglichkeit als diese 
'Glaskugel'. Daher musst du beim fscanf Aufruf die Dinge so einrichten, 
dass die fscanf Glaskugel (deren Verhalten bekannt ist) zum richtigen 
Schluss kommt bzw. du musst die Dinge so einrichten, dass du das 
Verhalten von fscanf berücksichtigst, wenn du die Argumente 
zusammenstellst.
fscanf passt sich nicht an dich an, sondern du musst dich an fscanf 
anpassen.
Genau darum ist es auch so wichtig, zu WISSEN wie in C gewisse Dinge 
funktionieren.

> Habe soweit möglich fast immer die gerade passende int größe verwendet..
> Damit alles möglichst klein gehalten wird. int wäre auf dem System
> 32bit.

Das ist alles löblich. Aber nicht die Ursache deines Problems.
#3655556
Lesenswert?

fscanfFehler schrieb:
> Okay, vielen Dank.
>
> Das bedeutet ich muss vorerst nur da nachschaun wo ich kein Standart-Int
> verwende aber mit Standart-Funktionen arbeite.

Ganz genau. Und speziell dort, wo variadische Funktionen im SPiel sind, 
wie zb printf oder scanf, die die Datentypen des Rests der Argumentliste 
aus einem anderen Argument herleiten müssen (wie es scanf ja mit dem 
Format String macht).
Bei anderen Funktionen hat der Compiler einen Prototyp und kann das 
überwachen. Bei variadischen Funktionen hat er das aber nicht. Manche 
Compiler prüfen zusätzlich und freundlicherweise den Formatstring gegen 
die restlichen Angaben. Aber du kannst dich nicht darauf verlassen. 
Daher ist es gut selbst die Regeln zu kennen.


Und PS: Standard schreibt sich mit 'D'. Das ist nicht die Kunst 
(englisch: art) einen (Verkaufs-) Stand zu bauen und hat auch nichts mit 
einer Standarte zu tun. Ich bin normal kein Rechtschreibnazi, aber 
diesen Fehler sieht man leider allzuoft.
Moderator #3655572
Lesenswert?

fscanfFehler schrieb:
> Dann muss ich mal schaun ob ich so einen Fehler nicht noch woanders
> habe...

Falls du den GCC benutzt, geht das ganz leicht: Der meckert dich nämlich
bei solchen Formatfehlern gerne an, wenn du ihn mit -Wformat (enthalten
in -Wall) darum bittest:
1
test.c:20:5: warning: format ‘%i’ expects argument of type ‘int *’, but argument 3 has type ‘int8_t *’ [-Wformat=]
2
     ret = fscanf (TMP2, "%i", &val);
3
     ^

Korrekt müsste die Zeile so heißen:
1
    ret = fscanf (TMP2, "%"SCNi8, &val);

Die SCN*-Makros gibt es für alle Integer-Typen und sind in inttypes.h
definiert. Entsprechend gibt es für printf die PRI*-Makros.
Gast #3655600
Lesenswert?

Wie ist es wenn ich system() benutze oder allgmein bei Funktionen die 
normal int zurückgeben?

Ich lasse meist die Ausgabe auf int_fast8_t zurückgeben.

Muss ich hier auch aufpassen oder geht es hier?
Moderator #3655620
Lesenswert?

fscanfFehler schrieb:
> diese Makros kannte ich noch gar nicht.
>
> Was machen die?

Sie expandieren zu den jeweils passenden scanf-Formaten. Bspw. ist SCNi8
auf meinem i83-PC als "hhi" definiert, so dass die obigen Zeile zu
1
    ret = fscanf (TMP2, "%hhi", &val);

> Bzw. kann dann val bei int8_t bleiben?

Ja.

fscanfFehler schrieb:
> Ich lasse meist die Ausgabe auf int_fast8_t zurückgeben.

Für int_fast8_t gibt es das Makro SCNiFAST8. Hier sind alle diese Makros
zusammengefasst (das # steht dabei für die Bitzahl, also 8, 16, 32 oder
64):
1
  PRIo#  PRIoLEAST#  PRIoFAST#  PRIoMAX  PRIoPTR
2
  PRIu#  PRIuLEAST#  PRIuFAST#  PRIuMAX  PRIuPTR
3
  PRIx#  PRIxLEAST#  PRIxFAST#  PRIxMAX  PRIxPTR
4
  PRIX#  PRIXLEAST#  PRIXFAST#  PRIXMAX  PRIXPTR
5

6
  SCNd#  SCNdLEAST#  SCNdFAST#  SCNdMAX  SCNdPTR
7
  SCNi#  SCNiLEAST#  SCNiFAST#  SCNiMAX  SCNiPTR
8
  SCNo#  SCNoLEAST#  SCNoFAST#  SCNoMAX  SCNoPTR
9
  SCNu#  SCNuLEAST#  SCNuFAST#  SCNuMAX  SCNuPTR
10
  SCNx#  SCNxLEAST#  SCNxFAST#  SCNxMAX  SCNxPTR
Gast #3656060
Lesenswert?

Der eine Fehler wurde ja hinreichend besprochen.
Korrekt also:
ret = (int8_t) fscanf (TMP2, "%i", &val);


Aber stehe ich irgendwie auf dem Schlauch?
Wieso macht man einen free() wenn man gar ein alloc() hat?
Kann so ein free() überhaupt funktionieren?
Ich behaupte mal nein.

Also ist es wohl so, daß die Funktion build_target einen Speicherbereich 
alloziert und nur den Zeiger zurückgibt.

Richtig?
Gast #3656081
Lesenswert?

> Korrekt also:

Nope, die korrekte Lösung steht weiter oben.

> Wieso macht man einen free() wenn man gar ein alloc() hat?

Das zugehörige [m,c,re]alloc() steht hier offenkundig in build_target(). 
Ist schlechtes Design, aber es funktioniert(tm).

> Kann so ein free() überhaupt funktionieren?
> Ich behaupte mal nein.

Doch[*]

HTH

[*] ..zumindest in bestimmten Fällen. Details dazu in der Doku.
Gast #3656216
Lesenswert?

Was wäre denn ein besseres Design??

Ich weiß das build_target() hier nicht so das gelbe vom ei ist ( besser 
wäre snprintf oder so) aber die funktion habe ich eigentlich für etwas 
anderes geschrieben aber hier konnte ich die einfach wieder verwenden 
und sie macht das was sie soll...


Wie wäre sowas besser gelöst?
Vorteil an build_target es ist alles dynamisch durch malloc geregelt.

Man sieht nur leider nciht das man free verwenden muss am ende der 
Funktion...
Gast #3656218
Lesenswert?

Vielleicht sollte ich es wirklich in etwas anderes abändern...
So muss ich auch 2 mal free machen..

mit snprintf nur einmal oder wenn ich ein festes Array mit [100] oder so 
nehme passt auch alles rein...

Und das Array gibt es ja nur innerhalb der funktion..
Gast #3656234
Lesenswert?

Hm.
Ich bin ja immer wieder erstaunt wie sehr sich Leute von hinten ins knie 
schießen für einen schritt nach vorn.
Wieso eigentlich nicht:
fopen Datei
Fread 1 byte
Fclose
If byte == '1'
    Return 1
Else
    Return 0
?
(Plus Fehlererkennung halt)
Anzahl benötigter alloc/free: 0
#3656443
Lesenswert?

dvbr schrieb:

> (Plus Fehlererkennung halt)

Wenn du dir seinen Code mal genauer ansiehst, dann sind 80% des Codes 
Fehlerbehandlung bzw. auf Fehlerbehandlung zurückzuführen, was an und 
für sich nicht ungewöhnlich ist.
Man könnte den Code anders organisieren, so dass er sich nicht so 
dermassen in die Länge zieht und auch optisch ansprechender aussieht. 
Insbesondere könnte man alle 3 Code-Abschnitte mit den frees zu einem 
einzigen zusammenfassen. Und auch sonst kann man noch so einiges 
kritisieren, wie zb die Ausgabe von Fehlertexten (die in einem der 
beiden Fälle dann noch nicht mal besonders hilfreich sind) in einer 
Low-Level Funktion. So was haut einem jegliches User-Interface über den 
Jordan. Aber das ist alles eine andere Geschichte.
Ob fscanf oder fgetc ist auch schon egal, zumal fscanf sich dann auch 
gleich noch um das 'Problem' von Whitespace-Charactern (Spaces und 
Zeilenumbrüche) kümmert, das du geflissentlich ignoriert hast. Genauso 
wie die Behandlung von Lesefehlern bzw. EOF

Die Allokierung hat damit allerdings überhaupt nichts zu tun. Die 
entsteht nur deshalb, weil er keine fixen Dateinamen hat, sondern die 
zur Laufzeit zusammenstoppeln muss. Und da gibt es nun mal kein 
Patentrezept. Entweder der Aufrufer der Funktion gibt den Speicher vor 
(Problem: woher weiß er wie groß der sein muss), oder die Funktion 
allokiert ihn dynamisch (Problem: der Aufrufer darf nicht auf den free 
vergessen; Problem: dynamische Allokierung kann schief gehen)
Gast #3656447
Lesenswert?

Wie kann ich die denn ohne goto zusammenlegen?

Uns wurde beigebracht keine gotos!

Daher hab ich des öfteren so lange free Abschnitte...

Hab es jetzt auch umgebaut auf snprintf.

Muss also nur noch einmal Speicher anfordern und habe keine 2 
Funktionsaufrufe mehr drin.
Gast #3656452
Lesenswert?

Achja wegen Fehler Meldung.

Das ganze läuft später auf einem headless System. Daher sind die 
Meldungen nur für mich während ich meinen Code teste.

Und die Allokierung überprüfe ich auch immer schön und warte solange bis 
er Speicher hat.

Werden spätere mehrere parallele State-Machines die mit 
mutexen/semaphoren gesteuert werden, daher geht es wohl.
Gast #3656459
Lesenswert?

Hmm der Hinweis hat gereicht...

hätte man auch mal vorher drauf kommen können.... ;-)

Allerdings muss ich dann immer die Funktion bis dahin "ablaufen" und 
kann nicht füher mit return rausspringen.

Hm muss ich mir mal überlegen ob ich es nochmal umbaua.
Habe an mehreren Stellen so lange free(), = NULL usw.
#3656460
Lesenswert?

> Allerdings muss ich dann immer die Funktion bis dahin "ablaufen" und
> kann nicht füher mit return rausspringen.

Warum sollte man das in diesem Fall auch wollen? So tief sind die 
Schachtelungen der if noch nicht. :-)
1
int_fast8_t get_value_gpio(char const * const gpionum)
2
{
3
  FILE* TMP2 = NULL;
4
  char * Target = build_target ("/sys/class/gpio/gpio", gpionum);
5
  char * Target2 = build_target (Target,"/value");
6
  int8_t val = -1;
7

8
  // Target wird nicht mehr gebraucht, kann also sofort freigegeben werden
9
  free (Target);
10

11
  TMP2 = fopen (Target2, "r");
12

13
  if( TMP2 )
14
  {
15
    if (fscanf (TMP2, "%"SCNi8, &val) != 1)
16
    {
17
      printf ("Cant read correct value from '%s'", Target2);
18
      val = -2;
19
    }
20

21
    fclose (TMP2);
22
  }
23
  else
24
    printf ("Could not open '%s'\n", Target2);
25

26
  free (Target2);
27
  return val;
28
}
#3656475
Lesenswert?

Kaj schrieb:
> fscanfFehler schrieb:
>> Uns wurde beigebracht keine gotos!
> Uns auch... aber schau mal in Treiber/den Linux Kernel: das Zeug ist
> voll von goto. goto ist gar nicht so boese wie immer alle sage.

Wenn man weiß, was man tut, dann nicht.
Aber Neulinge wissen eben selten im Detail, was sie tun bzw. anrichten
Zumal der Linux Kernel etwas anderen Gesetzmässigkeiten unterliegt. Der 
muss 'volle Kanne' laufen und da sehen x Leute über den Code drüber, ehe 
er freigegeben wird.

Es gibt durchaus Fälle, in denen ein goto die einfachste und klarste 
Lösung ist. Ist mir in 30 Jahren genau 1 mal untergekommen. Wobei man 
durchaus über den Begriff 'klar' diskutieren kann.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren