Entwickler-Ecke

Basistechnologien - Pointer Anwendungsbeispiele


lapadula - Mo 17.07.17 15:10
Titel: Pointer Anwendungsbeispiele
Hallo, habe mich ein wenig über Pointer informiert verstehe jetzt aber nicht genau wozu man diese in c# nutzen soll.

Man hat mehr Kontrolle indem ich selber auf die Speicheradressen zugreifen kann aber wie sieht das an einem Konkreten Beispiel aus.

Was nützt es mir zu wissen in welchen Speicherbereich sich meine Variable x befindet?

Mfg


hydemarie - Mo 17.07.17 17:27

Zu wissen, wo deine Variable sich befindet, ist nicht der einzige Vorteil.

Um Pointer zu verstehen, sollte man aufhören, in managed code wie C# zu denken. (Außerdem: Mitunter hast du es mit Hardware zu tun, auf der C# nicht läuft. Gerade auf embedded-Systemen kommst du mit C# oft nicht besonders weit.)

Denken wir mal etwas tiefer, denken wir in C.

Zitat:
wie sieht das an einem Konkreten Beispiel aus


Einfaches (weltfremdes, weil das so wahrscheinlich kaum jemand machen würde) Beispiel: Addition zweier Zahlen. Das kannst du entweder einfach oder effizient machen.


C++-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
#include <stdio.h>

int addiere_ohne_pointer(int a, int b) {
    return a + b;
}

int addiere_mit_pointer(int* a, int* b) {
    return *a + *b;
}

int main(void) {
    int func1, func2;
    int val1 = 1, val2 = 1;

    func1 = addiere_ohne_pointer(val1, val2);
    func2 = addiere_mit_pointer(&val1, &val2);

    printf("%d / %d", func1, func2); /* sollte "2 / 2" ausgeben */
    
    return 0;
}


Fragen? :D


Christian S. - Mo 17.07.17 20:23

user profile iconlapadula hat folgendes geschrieben Zum zitierten Posting springen:
Hallo, habe mich ein wenig über Pointer informiert verstehe jetzt aber nicht genau wozu man diese in c# nutzen soll.

Am Besten gar nicht ;-) Das Arbeiten mit Pointern ist einer managed Umgebung wie .NET ein notwendiges Übel, dem man aber so weit wie möglich aus dem Weg gehen sollte. Es widerspricht den grundlegenden Ideen von .NET, dass man eben nicht mehr direkt auf Speicherbereiche zugreift. Insbesondere wenn man auf Funktionen der Windows API zugreifen muss, kommt man nicht drum herum, ansonsten sind die Anwendungsszenarien aber begrenzt.

Man kann, wenn man mit Pointern und generell mit unsafe-Codebereichen arbeitet, vielleicht ein wenig Performance herausholen, aber meist steht der Gewinn an Performance auf der einen und der Verlust an Lesbarkeit und Sicherheit auf der anderen Seite in keinem sinnvollen Verhältnis. Wessen Anwendung es erfordert, auf diesem Level an der Performance zu schrauben, sollte eher erwägen, direkt auf native Sprachen wie C, C++ oder Delphi zu setzen.

Mit diesen Warnungen vorweg, kannst Du Dir ein Beispiel, welches in den Bereich der Windows-API-Aufrufe fällt bei uns in den Open-Source-Projekten angucken, wo user profile iconFrühlingsrolle in seinem Projekt frDriveNET [https://www.entwickler-ecke.de/topic_frDriveNET_116499.html] einiges mit IntPtr anstellt.

Wenn Du wirklich an anderer Stelle Pointer verwenden möchtest, sähe ein Beispiel so aus:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
            int[] allTheBestNumbers = new int[42];

            fixed(int* pAllTheBestNumbers = allTheBestNumbers)
            {
                pAllTheBestNumbers[44] = 42;
            }

            MessageBox.Show(allTheBestNumbers[44].ToString());

Wichtig: der Code funktioniert nur, wenn (a) das Projekt mit /unsafe compiliert wird und (b) die Methode, in der der Code steht, mit unsafe deklariert wird. Man sieht, dass die Hürden hier extra hoch gehängt werden.

Der Code zeigt, dass durch die Verwendung von Pointern z.B. die Range Checks von .NET ausgehebelt werden und ich auf das 45. Element eines 42 Elemente langen Arrays zugreifen kann. Das spart Zeit (weil keine Checks ausgeführt werden), kann aber auch dazu führen, dass man in Speicherbereiche schreibt, wo man nicht hin will.

Die Range Checks greifen dann wieder in der letzten Zeile, wo ich direkt auf das Array zugreife.


jfheins - Mo 17.07.17 21:29

user profile iconlapadula hat folgendes geschrieben Zum zitierten Posting springen:
Man hat mehr Kontrolle indem ich selber auf die Speicheradressen zugreifen kann aber wie sieht das an einem Konkreten Beispiel aus.

Was nützt es mir zu wissen in welchen Speicherbereich sich meine Variable x befindet?

Die Adresse nützt dir tatsächlich relativ wenig.

Was dir was bringen kann: Bei einem Arrayzugriff prüft C# immer den Index, ob er innerhalb der Grenzen ist. Wenn nicht, kommt die IndexOutOfRangeException.

Nun könnte es sein, dass du ein Array mit 1 Milliarde Elementen hast und alle mal 2 nehmen willst. (Nur ein Beispiel) Wenn du also

C#-Quelltext
1:
2:
3:
4:
for (int i = max; i >= min; i--)
{
  arr[i] = arr[i] * 2;
}


schreibst, hast du für jede Multiplikation im Hintergrund zwei if-Abfragen. Wenn das problematisch ist, könntest du unsafe benutzen. Hier umgehst du diese Prüfungen, was im Endeffekt schneller sein kann (nicht muss).

Nur musst du dann viel mehr darauf achten, dass die Grenzen (hier: min und max) auch wirklich in jedem Fall stimmen, weil ein Problem nicht mehr direkt als Exception gemeldet wird.


Delete - Mo 17.07.17 22:03

- Nachträglich durch die Entwickler-Ecke gelöscht -


C# - Di 18.07.17 09:04

Off-Topic

@jfheins

Hab es gerade mal getestet:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
        static void IterateChecked(int[] array)
        {
            for (int i = 0; i < array.Length; i++)
                array[i] = (int) (3 * array[i] + 4 * Math.Sin(i));
        }

        static unsafe void IterateUnchecked(int[] array)
        {
            fixed (int* begin = &array[0])
            {
                int* end = begin + array.Length;
                for (int* it = begin; it < end; it++)
                    *it = (int) (3 * *it + 4 * Math.Sin(*it));
            }
        }

        static void Main(string[] args)
        {
            const int size = 10_000_000;
            const int loops = 20;

            var rdm = new Random();
            var uncheckedAccess = new List<TimeSpan>();
            var checkedAccess = new List<TimeSpan>();

            for (int i = 0; i < loops; i++)
            {
                var array = Enumerable.Range(0, size).Select(j => rdm.Next(01000)).ToArray();

                var watch = Stopwatch.StartNew();
                IterateChecked(array);
                watch.Stop();
                checkedAccess.Add(watch.Elapsed);

                array = Enumerable.Range(0, size).Select(j => rdm.Next(01000)).ToArray();

                watch.Restart();
                IterateUnchecked(array);
                watch.Stop();
                uncheckedAccess.Add(watch.Elapsed);
            }

            Console.WriteLine($"Checked access for {size:N0} elements:   {checkedAccess.Average(s => s.TotalMilliseconds):F3}ms");
            Console.WriteLine($"Unchecked access for {size:N0} elements: {uncheckedAccess.Average(s => s.TotalMilliseconds):F3}ms");

            Console.ReadKey();
        }


Bei mir kommt das raus:

Quelltext
1:
2:
Checked access for 10.000.000 elements:   166,493ms
Unchecked access for 10.000.000 elements: 200,671ms


Hier [https://stackoverflow.com/questions/16713076/array-bounds-check-efficiency-in-net-4-and-above] gibt es auch eine gute Erklärung warum das so ist (die oberen beiden Antworten).

Off-Topic ende