Przypisywanie dziwnych wartości do wskaźnika na następny węzeł

0

Czołem. Bawię się teraz wskaźnikami w C# i za cholerę nie mogę pojąć, czemu instancja obiektu List przechowująca wskaźniki do poprzedniego i następnego węzła, po dodaniu wartości do wskaźnika węzła o nazwie Head, w mainie w instancji List wywala mi jakieś dziwne adresy w pamięci mimo że tam null pakuje. Może ktoś zna temat wskaźników w C# i pomoże :D Na kodzie łatwiej będzie zrozumieć:

namespace List
{
    unsafe public struct Node
    {
        public Node(int value, Node* prev = null, Node* next = null)
        {
            Value = value;
            PreviousNode = prev;
            NextNode = next;
        }

        public int Value;
        public Node* PreviousNode;
        public Node* NextNode;
    }

    unsafe public class List
    {
        public Node* Head;
        public Node* Tail;
        public int Count;

        public List()
        {
            Head = Tail = null;
        }

        unsafe public void Add(int value)
        {
            if (Head == null)
            {
                Node temp = new Node(value);
                Head = &temp;
            }
            else
            {
                if (Tail == null)
                {
                    *Tail = new Node(value, Head, null);
                    Head->NextNode = Tail;
                }
                else
                {
                    Node* temp = Tail;
                    *Tail = new Node(value, temp, null);
                }
            }
            Count++;
        }
    }
}
namespace ListApp
{
    class Program
    {
        unsafe static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            List.List list = new List.List();
            list.Add(1); //przy debugowaniu w klasie List tutaj jest ok, we wskaźniku jest obiekt z nullami do następnego i poprzedniego węzła
            Console.WriteLine(list.Count); /* w tym momencie list.Head->NextNode ma przypisane jakieś przypadkowe adresy w pamięci i każdy ten obiekt ma kolejny randomowy NextNode z pamięci i randomową wartość */
            Console.WriteLine(list.Head->Value); 
            Console.ReadKey();
        }
    }
}
0

Gdzies Ty polazł?
na cholerę CI unsafe wskaźniki w C#?

0

Byłem na rozmowie o pracę jako programista C# i było pytanie o wskaźniki, więc widocznie ich używają. Także pomyślałem że dobrze będzie się dokształcić w tym temacie.

0
gruby907 napisał(a):

Byłem na rozmowie o pracę jako programista C# i było pytanie o wskaźniki, więc widocznie ich używają. Także pomyślałem że dobrze będzie się dokształcić w tym temacie.

To się kształć.
Nie zrozumiesz, dopóki nie zgłębisz istoty "unsafe" (danych niezarządzanych itd, itd, itd) - którego NIGDY nie używa się w kodzie aplikacyjnym

0

jak widać chyba ktoś jednak używa. Wiem co to znaczy unsafe i czym grozi używanie wskaźników. Pytam o czysto techniczną rzecz.

0

ciekawe co na to @Wibowit który mówił że normalnie w C# się nie używa raw pointerów, a tylko w benchmarkach chcą nabić wydajności ;)

0
WeiXiao napisał(a):

ciekawe co na to @Wibowit który mówił że normalnie w C# się nie używa raw pointerów, a tylko w benchmarkach chcą nabić wydajności ;)

To ktoś zamierza użyć kodu OPa w aplikacji biznesowej?

Poza tym, o ile dobrze rozumiem to ten kod jest totalnie popsuty. Autor wyciąga wskaźnik do obiektu zarządzanego, czyli takiego który jest przesuwany, kopiowany, usuwany, etc przez GC. To ma być dobre?

4

Node jest strukturą, więc jest alokowany na stosie (linia 32). Jak weźmiesz do tego wskaźnik i opuścisz funkcję, to tracisz ramkę stosu, potem następna funkcja nadpisuje ten obszar pamięci (czyli przy wołaniu Console.WriteLine nadpisujesz węzły).

0
Afish napisał(a):

Node jest strukturą, więc jest alokowany na stosie (linia 32). Jak weźmiesz do tego wskaźnik i opuścisz funkcję, to tracisz ramkę stosu, potem następna funkcja nadpisuje ten obszar pamięci (czyli przy wołaniu Console.WriteLine nadpisujesz węzły).

kurde w C++ to było łatwiejsze :D to tak w skrócie: da się jakoś zrobić żeby wskaźników używać i żeby z poziomu Maina był cały czas ten sam obiekt?

4

Ogólnie obiekty przesuwają się dowolnie w pamięci zarządzanej i ich adres może się zmieniać dynamicznie w dowolnym momencie, dlatego nie możesz po prostu przechowywać wskaźników w ten sposób. Potrzebujesz co najmniej słówka "fixed" i przypinania obiektów GCHandle.Alloc(myObject, GCHandleType.Pinned)

Prawdopodobnie firmie bardziej chodziło o wskaźniki IntPtr wykorzystywane w łączeniu kodu zarządzanego z niezarządzanym / PInvoke. Mimo że większość przykładów na Internecie operuje na IntPtr, to praktycznie wszędzie można je wymienić na SafeHandle które są automatycznie zwalniane przez GC - przykładowo we wszystkich wywołaniach WinApi można zwyczajnie zamienić IntPtr na SafeHandle i Marshaller się zajmie opakowaniem wskaźników i tym samym pozwoli uprościć kod o zwalnianie tych wskaźników (lub zwyczajne użycie ich w bloku using)

Używanie zwykłych wskaźników jest właściwie nigdzie niepraktykowane, niebezpieczne i nie przynosi wymiernych korzyści. Jeśli chodzi o performance to od C#7 masz dostępne klasy Span<T> i Memory<T> które w bezpieczny sposób mogą zastąpić operacje na wskaźnikach dla tablic i spokojnie dorównują im szybkością. Nie ma żadnego uzasadnienia dla używania pointerów w zarządzanym kodzie, chyba że prosisz się o problemy.

1

Musisz go zaalokować na stercie (czyli nie może być strukturą, albo musisz go zboxować), potem wypadałoby go przypiąć (GCHandle.Alloc z opcją Pin) i wtedy dopiero brać wskaźnik. To znacząco ogranicza dostępność używanych typów, bo nie wszystko można pinować (ale to runtime krzyknie).

Możesz też alokować na LOH (zrób odpowiednio dużą tablicę i tam alokuj strukturę), ale od dotneta 4.5.1 (czy coś koło tego) można też kompaktować LOH-a, więc nie masz gwarancji, że dane nie zostaną przesunięte. Nie wiem, czy struktury można pinować, ale strzelałbym, że nie (szczególnie, że GCHandle pewnie zrobi boxing takowej przy wywołaniu Alloc).

Możesz też zaalokować pamięć natywną, taką poza GC, zaalokować strukturę tam (to pewnie wymagałoby ręcznej zabawy wskaźnikami lub TypedReferences lub Marshal) i wtedy brać wskaźniki.

0

Wskaźniki w C# mają dwa zastosowania, sensowniejsze - integracja z jakimiś libkami C++, mniej sensowne - przetwarzanie bloków pamięci.
Jeśli ta firma pisze swoje własne kolekcje na wskaźnikach, to uwierz mi - i tak byś nie chciał tam pracować.

0

Klasy NET zawierają mnóstwo takich wskaźników i kod niezabezpieczony . Tak czy inaczej pisząc programy w C# korzystasz ze wskaźników.
Tutaj masz kody źródłowe :
https://referencesource.microsoft.com/

1
Zimny Krawiec napisał(a):

Klasy NET zawierają mnóstwo takich wskaźników i kod niezabezpieczony . Tak czy inaczej pisząc programy w C# korzystasz ze wskaźników.

Pisząc w C# korzysta się też z uporządkowanego ruchu elektronów, ale to nie powód, aby samemu wkładać widelec do gniazdka.

0
Zimny Krawiec napisał(a):

Klasy NET zawierają mnóstwo takich wskaźników i kod niezabezpieczony . Tak czy inaczej pisząc programy w C# korzystasz ze wskaźników.
Tutaj masz kody źródłowe :
https://referencesource.microsoft.com/

Od kiedy implementacja biblioteki standardowej jest wzorcem do naśladowania?
Sprawdziłem LinkedListę. Nie wiem czy to jest ta standardowa implementacja, ale żadnego unsafe nie zauważyłem (a w szczególności wskaźników): https://referencesource.microsoft.com/#System/compmod/system/collections/generic/linkedlist.cs

1 użytkowników online, w tym zalogowanych: 0, gości: 1