Forum: PC-Programmierung Zeiger auf Argumente und lokale Variablen


von misra (Gast)


Lesenswert?

Hello, hello

in diesem Video

https://www.youtube.com/watch?v=ojEXMM_1bVA

geht es um C und Rust. Bei 25:40 wird ein C-Beispiel gezeigt, welches 
wohl gegen die MISRA-Regel 18.6 verstößt. Warum eigentlich? Das 
Funktionsargument ist doch selbst ein Zeiger und verweist auf keine 
lokale Variable.
1
//program 1
2
3
int* p_global;
4
5
void foo(int* p_local){
6
  p_global=p_local; //ist wohl nicht in Ordnung
7
}

Ich kann ja sowas schreiben:
1
//program 2
2
3
int* p;
4
5
void test (void)
6
{
7
   int a = 123;
8
   p = &a;
9
}
10
11
int main (void)
12
{
13
   test();
14
   printf ("a: %d\n", *p);
15
   
16
   return 1;
17
}

Und das wäre ja falsch. Da wird auf eine lokale Variable später 
zugegriffen.

Dann dachte ich (beim Programm 1), dass es da doch Probleme geben kann. 
Z.B. wenn eine Funktion eine andere aufruft und da einen Zeiger auf eine 
lokale Variable übergibt:
1
//program 3
2
3
int* p;
4
5
void
6
bar (int* x)
7
{
8
   p = x;
9
}
10
11
void
12
foo (void)
13
{
14
   int x = 42;
15
   bar (&x);
16
}
17
18
int main (void)
19
{
20
   foo();
21
   printf ("p-val: %d\n", *p);
22
23
   return 1;
24
}

Hier hatte ich tatsächlich eine Fehlermeldung erwartet, aber da gibt es 
nicht mal eine Warnung und es wird 42 ausgegeben (weil der Speicherplatz 
noch nicht überschrieben wurde?).

p zeigt doch auf eine lokale Variable. Braucht der Compiler irgendwelche 
speziellen Parameter (bei mir ist schon einiges aktiviert)? Wieso gibt 
es keine Warnung?

von Nils (Gast)


Lesenswert?

Nun ja, Du speicherst ein Pointer auf eine lokale Variable global ab. 
Sobald Du die Funktion, in der die lokale Variable definiert ist, 
beendest darfst Du nicht mehr auf die Kopie zugreifen.

Dein Test zeigt zwar, das es manchmal funktioniert, aber das ist reine 
Glückssache (und undefiniertes Verhalten).

Hier mal ein Gegenbeispiel von Program2:
1
int* p;
2
3
void test (void)
4
{
5
   int a = 123;
6
   p = &a;
7
}
8
9
int main (void)
10
{
11
   test();
12
13
   // das hier ist neu:
14
   printf ("lets do some stuff here\n");
15
   
16
   printf ("a: %d\n", *p);
17
   
18
   return 1;
19
}

Wenn Du das laufen lässt wirst Du feststellen, das nicht mehr 123 
ausgegeben wird, sondern irgend etwas anderes.

Das hängt damit zusammen, dass das zusätzliche printf Statement auch 
Stack braucht und den gleichen Speicher benutzt, der vorher in Funktion 
test() für die Variable a genutzt wurde.

Solche unsichtbare Seiteneffekte möchte man im Code nicht haben, daher 
gibt es diese MISRA Regel.

von Nils (Gast)


Lesenswert?

Wenn Du das Program mit einem Memory Checker wie z.B.  Valgrind laufen 
lässt bekommst Du übrigens die Warnungen. Mein Linux spuckt z.B. dieses 
Fehler Log aus:
1
nils@doofnase ~ $ valgrind ./a.out 
2
==18285== Memcheck, a memory error detector
3
==18285== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
4
==18285== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
5
==18285== Command: ./a.out
6
==18285== 
7
lets add some stuff here
8
==18285== Conditional jump or move depends on uninitialised value(s)
9
==18285==    at 0x4E87B93: vfprintf (vfprintf.c:1631)
10
==18285==    by 0x4E8F8A8: printf (printf.c:33)
11
==18285==    by 0x400642: main (in /home/nils/a.out)
12
==18285== 
13
==18285== Use of uninitialised value of size 8
14
==18285==    at 0x4E8477B: _itoa_word (_itoa.c:179)
15
==18285==    by 0x4E8813C: vfprintf (vfprintf.c:1631)
16
==18285==    by 0x4E8F8A8: printf (printf.c:33)
17
==18285==    by 0x400642: main (in /home/nils/a.out)
18
==18285== 
19
==18285== Conditional jump or move depends on uninitialised value(s)
20
==18285==    at 0x4E84785: _itoa_word (_itoa.c:179)
21
==18285==    by 0x4E8813C: vfprintf (vfprintf.c:1631)
22
==18285==    by 0x4E8F8A8: printf (printf.c:33)
23
==18285==    by 0x400642: main (in /home/nils/a.out)
24
==18285== 
25
==18285== Conditional jump or move depends on uninitialised value(s)
26
==18285==    at 0x4E881BF: vfprintf (vfprintf.c:1631)
27
==18285==    by 0x4E8F8A8: printf (printf.c:33)
28
==18285==    by 0x400642: main (in /home/nils/a.out)
29
==18285== 
30
==18285== Conditional jump or move depends on uninitialised value(s)
31
==18285==    at 0x4E87C69: vfprintf (vfprintf.c:1631)
32
==18285==    by 0x4E8F8A8: printf (printf.c:33)
33
==18285==    by 0x400642: main (in /home/nils/a.out)
34
==18285== 
35
==18285== Conditional jump or move depends on uninitialised value(s)
36
==18285==    at 0x4E8842A: vfprintf (vfprintf.c:1631)
37
==18285==    by 0x4E8F8A8: printf (printf.c:33)
38
==18285==    by 0x400642: main (in /home/nils/a.out)
39
==18285== 
40
==18285== Conditional jump or move depends on uninitialised value(s)
41
==18285==    at 0x4E87CBB: vfprintf (vfprintf.c:1631)
42
==18285==    by 0x4E8F8A8: printf (printf.c:33)
43
==18285==    by 0x400642: main (in /home/nils/a.out)
44
==18285== 
45
==18285== Conditional jump or move depends on uninitialised value(s)
46
==18285==    at 0x4E87CF2: vfprintf (vfprintf.c:1631)
47
==18285==    by 0x4E8F8A8: printf (printf.c:33)
48
==18285==    by 0x400642: main (in /home/nils/a.out)

von misra (Gast)


Lesenswert?

Warum gibt es keine Warnung  oder Fehlermeldung?

Und Programm 1 ist gefährlich weil die Aufrufe (wie in Programm 3) 
verschachtelt werden können? Denn grundsätzlich ist Programm 1 korrekt.

von misra (Gast)


Lesenswert?

Nils schrieb:
> Valgrind

Muss ich noch schauen ;-)

Danke!

Ich benutze zwei dutzend Paramater (-Wall -Wextra -Wvla -Wundef 
-pedantic        -fno-omit-frame-pointer -fsanitize=address  und, und, 
und), aber eine Warnung gibt es trotzdem nicht.

von DPA (Gast)


Lesenswert?

misra schrieb:
> -fsanitize=address

Asan ist inkompatibel mit valgrind. Man kann immer nur eins von beiden 
nutzen.

von Nils (Gast)


Lesenswert?

Grundsätzlich ist Program 1 valider C-Code. Das Problem entsteht erst 
dann, wenn Du den Pointer p_global dereferenzierst (was Du in Program1 
nicht machst).

Warum es keine Warnungen gibt? Tja, das ist bei vielen Compilern leider 
so. Theoretisch könnte man diesen Fall erkennen und warnen. Allerdings 
würden solche Analysen bei einem nicht trivalen Programm recht lange 
dauern.

Dafür gibt es ja statische Code-Analyse wie z.B. den MISRA-Checker, Lint 
oder cppcheck. Die prüfen viel penibler und erkennen solche Probleme.

Macht man in der Praxis aber nicht bei jedem Entwicklungsschritt, 
sondern bei Bedarf oder auch automatisch über Nacht.

von Rolf M. (rmagnus)


Lesenswert?

Nils schrieb:
> Grundsätzlich ist Program 1 valider C-Code. Das Problem entsteht erst
> dann, wenn Du den Pointer p_global dereferenzierst (was Du in Program1
> nicht machst).

Ein Problem entsteht nur, wenn das Objekt, auf das der Pointer zeigt, 
bei Dereferenzierung nicht mehr existiert. In dem Programmausschnitt 
(ein komplettes Programm ist es nicht) sieht man nicht, wo das Objekt 
herkommt, weil die Funktion nicht aufgerufen wird.

misra schrieb:
> Ich benutze zwei dutzend Paramater (-Wall -Wextra -Wvla -Wundef
> -pedantic        -fno-omit-frame-pointer -fsanitize=address  und, und,
> und), aber eine Warnung gibt es trotzdem nicht.

Auch -O3? Einige Optimierungen erfordern eine deutlich erweiterte 
Code-Analyse, die auch dazu führt, dass der Compiler mehr Probleme 
dieser Art erkennen kann.

von A. S. (Gast)


Lesenswert?

misra schrieb:
> Warum gibt es keine Warnung  oder Fehlermeldung?

Weil ein C- Compiler kompiliert. Seit mehr als 40 Jahren ist für die 
Warnungen LINT zuständig.

Ein Compiler bekommt ein C-File und soll daraus schnell effektiven 
Assembler machen.

Lint (und andere statische Codeanalyse-Tools) bekommen alle C-Files und 
prüfen nach individuellen Regeln ob typische Fehler auftreten.

Dein erstes Beispiel ist wenn, dann nur im Kontext des Aufrufs kritisch. 
Und der Aufruf kann in einem anderen C-File sein, dass der Compiler bei 
seinem Durchlauf nicht kennt. Wir auch nicht.

von Rolf M. (rmagnus)


Lesenswert?

A. S. schrieb:
> misra schrieb:
>> Warum gibt es keine Warnung  oder Fehlermeldung?
>
> Weil ein C- Compiler kompiliert. Seit mehr als 40 Jahren ist für die
> Warnungen LINT zuständig.

In den letzten Jahren hat gcc allerdings viele Warnungen eingeführt, die 
eher zu einem lint-Tool passen.

von misra (Gast)


Lesenswert?

misra schrieb:
> Bei 25:40 wird ein C-Beispiel gezeigt

Da steht eigentlich auch die Regel:

"The address of an object with automatic storage shall not be copied to 
another object that persists after the first object has ceased to exist"

Somit ist wohl nicht das gemeint:
1
//program 1
2
3
int* p_global;
4
5
void foo(int* p_local){
6
  p_global = p_local;
7
}

sondern das:
1
//program 1
2
int** pp_global;
3
4
void foo(int* p_local){
5
  pp_global = &p_local; 
6
}

Wahrscheinlich wurde im Video ein falsches Beispiel für diese Regel 
gezeigt.

by the way:
automatic storage ist ja das:
1
int
2
foo (void)
3
{
4
   auto int x = 42;
5
6
   return x;
7
}

Also irgendeine Variable in einem Block. Also kann man "auto" explizit 
dazuschreiben.

Auch die Funktionsparameter haben auto storage class.

Warum geht dann das nicht?
1
int
2
foo (auto int x)
3
{
4
   return x + 1;
5
}

Meldung: invalid storage class specifier in function declarator foo 
(auto int x)

von Rolf M. (rmagnus)


Lesenswert?

misra schrieb:
> Warum geht dann das nicht?
> int
> foo (auto int x)
> {
>    return x + 1;
> }
>
> Meldung: invalid storage class specifier in function declarator foo
> (auto int x)

Wozu sollte man die bei einem Funktionsparameter angeben? Der kann im 
Gegensatz zu einer lokalen Variable ja nicht static (was der Gegenpart 
zu auto wäre) sein.

von misra (Gast)


Lesenswert?

weil die Funktionsparameter zum auto storage class gehören. Dann sollten 
sie auch den expliziten auto-specifier akzeptieren (wie es jede lokale 
Variable tut).

von Einer (Gast)


Lesenswert?

Nils schrieb:
> Allerdings
> würden solche Analysen bei einem nicht trivalen Programm recht lange
> dauern.

Solche Analysen sind bei nicht trivialen Programme unmöglich.

Siehe auch Halteproblem.

von Rolf M. (rmagnus)


Lesenswert?

Einer schrieb:
> Solche Analysen sind bei nicht trivialen Programme unmöglich.

Kommt drauf an, was für dich trivial ist. Anders formuliert: Solche 
Analysen sind nicht für jedes beliebige Programm möglich.

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.