Zeiger auf Argumente und lokale Variablen

Gast #6829035
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?
Gast #6829044
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.
Gast #6829049
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)
Gast #6829056
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.
#6829090
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.
Gast #6829119
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.
Gast #6829616
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)
#6829673
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.

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