Uzyskanie adresu pamięci struktury

Uzyskanie adresu pamięci struktury
AN
  • Rejestracja:prawie 19 lat
  • Ostatnio:7 minut
0

W jaki sposób prawidłowo uzyskać adres w pamięci operacyjnej, pod którym zapisany jest obiekt?

Ja właśnie robię próby z manipulacją na obiektach (klonowanie, pożyczanie, przenoszenie własności itp) i paru rzeczy nie rozumiem.

Znalazłem dwa sposoby uzyskania tego adresu: https://stackoverflow.com/questions/35882994/is-there-any-way-to-get-the-address-of-a-struct-in-rust

Adres to chciałbym wyświetlać jako numer, który jednoznacznie określa dany obiekt. Przede wszystkim chodzi o to, żeby między innymi wypisać każdy przypadek utworzenia i zniszczenia obiektu. Ten numer jednoznacznie powinien wskazać, jest jest to jeden i ten sam obiekt.

Ostatecznie mam taki program:

Kopiuj
struct Book
{
    title: String,
}

impl Book
{
    pub fn set_title_borrow(&mut self, tit : &String)
    {
        self.title = tit.clone();
    }

    pub fn set_title_move(&mut self, tit : String)
    {
        self.title = tit;
    }
    
    pub fn addr1(&self) -> usize
    {
        return (self as * const _) as usize;
    }

    pub fn addr2(&self) -> usize
    {
        return std::ptr::addr_of!(self) as usize;
    }

    pub fn new(name: String) -> Self {
        let book: Book = Book {title: name};

        println!("* Tworzenie 1        {} {} {}", book.addr1(), book.addr2(), book.title);
        book
    }
    
    pub fn printaddr(&self)
    {
        println!("  Adres              {} {} {}", self.addr1(), self.addr2(), self.title);
    }
}

impl Drop for Book {
    fn drop(&mut self) {
        println!("# Niszczenie         {} {} {}", self.addr1(), self.addr2(), self.title);
    }
}

impl Clone for Book {
    fn clone(& self) -> Book {
        println!("* Klonowanie       z {} {} {}", self.addr1(), self.addr2(), self.title);
        
        let mut t = self.title.clone();
        t.push_str(" kopia");

        let cl = Book {title: t};
        
        println!("                  do {} {} {}", cl.addr1(), cl.addr2(), cl.title);
        cl
    }
}


fn create_book_and_return_it_with_ownership() -> Book
{
    let book: Book = Book {title: String::from("Nowy")};

    println!("* create_book_and_   {} {} {}", book.addr1(), book.addr2(), book.title);
    book
}

fn take_ownership(book: Book) -> Book
{
    println!("  take_owner stary   {} {} {}", book.addr1(), book.addr2(), book.title);
    let x = book.clone();
    println!("  take_owner nowy    {} {} {}", x.addr1(), x.addr2(), x.title);
    return x;
}

fn borrow_test(book: &Book)
{
    println!("  borrow_test        {} {} {}", book.addr1(), book.addr2(), book.title);
}


fn main()
{
    let book3 = Book::new(String::from("Trzeci"));
    book3.printaddr();
    let mut book = create_book_and_return_it_with_ownership();
    book.printaddr();
    book.set_title_move(String::from("Czwarty"));
    book.printaddr();
    let tit = String::from("Piaty");
    book.set_title_borrow(&tit);
    book.printaddr();
    book.title = String::from("Pierwszy");
    book.printaddr();
    book = take_ownership(book);
    book.printaddr();
    borrow_test(&book);
    book.printaddr();
    let mut book2 = book.clone();
    book.printaddr();
    book2.printaddr();
    book2.title = String::from("Drugi");
    book.printaddr();
    book2.printaddr();
    book.printaddr();
    book2.printaddr();
}

Uzyskałem taki wynik, ponumerowałem linie dla czytelności:

Kopiuj
1.  * Tworzenie 1        140736324356480 140736324356432 Trzeci
2.    Adres              140736324356816 140736324356496 Trzeci
3.  * create_book_and_   140736324356464 140736324356416 Nowy
4.    Adres              140736324356864 140736324356496 Nowy
5.    Adres              140736324356864 140736324356496 Czwarty
6.    Adres              140736324356864 140736324356496 Piaty
7.    Adres              140736324356864 140736324356496 Pierwszy
8.    take_owner stary   140736324356992 140736324356144 Pierwszy
9.  * Klonowanie       z 140736324356992 140736324355408 Pierwszy
10.                   do 140736324355696 140736324355408 Pierwszy kopia
11.   take_owner nowy    140736324356392 140736324356144 Pierwszy kopia
12. # Niszczenie         140736324356992 140736324355808 Pierwszy
13.   Adres              140736324356864 140736324356496 Pierwszy kopia
14.   borrow_test        140736324356864 140736324356496 Pierwszy kopia
15.   Adres              140736324356864 140736324356496 Pierwszy kopia
16. * Klonowanie       z 140736324356864 140736324356048 Pierwszy kopia
17.                   do 140736324356336 140736324356048 Pierwszy kopia kopia
18.   Adres              140736324356864 140736324356496 Pierwszy kopia
19.   Adres              140736324357024 140736324356496 Pierwszy kopia kopia
20.   Adres              140736324356864 140736324356496 Pierwszy kopia
21.   Adres              140736324357024 140736324356496 Drugi
22.   Adres              140736324356864 140736324356496 Pierwszy kopia
23.   Adres              140736324357024 140736324356496 Drugi
24. # Niszczenie         140736324357024 140736324356448 Drugi
25. # Niszczenie         140736324356864 140736324356448 Pierwszy kopia
26. # Niszczenie         140736324356816 140736324356448 Trzeci

Nie rozumiem następujących rzeczy:

  1. Dlaczego adres w liniach 1 i 2 się różni, mimo, że to jeden i ten sam obiekt? Tak samo linie 3 i 4.
  2. Dlaczego drugi adres w liniach 4, 5, 6 jest ten sam, mimo, że to są trzy różne obiekty?
  3. Dlaczego linie 7 i 8 mają różny adres, mimo, że to jeden i ten sam obiekt.

Wygląda na to, że adres uzyskany poleceniem (self as * const _) zmienia się za każdym razem, gdy obiekt wchodzi lub wychodzi z funkcji (mimo, że faktycznie nie następuje kopiowanie obiektu), a adres uzyskany funkcją std::ptr::addr_of!(self) może być taki sam dla kilku obiektów.

W jaki sposób prawidłowo uzyskać adres pamięci dla danego obiektu? Może pierwsza metoda źle podaje adres, bo zmienia się wtedy, kiedy faktycznie pozostaje bez zmian.

DR
  • Rejestracja:prawie 12 lat
  • Ostatnio:32 minuty
  • Postów:1129
2

Pomijam fakt, że kod wygląda jak Java XD

Mogę bredzić, ale w Rust nie można myśleć jak w "obiektowce". Wartości w rust są przenoszone "moved" I nie mają "jednego miejsca".

W teoim wypadku natomiast jak odpalisz program w trybie release, a nie debug, to pewnie dostaniesz te same adresy. Ma to związek z optymalizacja robioną przez kompilator.

Pisząc to, zdałem sobie sprawę jak mało wiem o tym języku, w którym już długo pisze...

elwis
  • Rejestracja:ponad 18 lat
  • Ostatnio:11 dni
1

Takie spaghetti, że się czytać nie chce. Funkcja addr1 to jest sposób, który bym zastosował, referencję rzutuję na wskaźnik, a potem na usize jak chcesz mieć taki typ… Uprość przypadek może…


RO
Już to sobie wyobrażam jak Linus Torvalds pomstuje gdy czyta kod rusta w kernerze linux.
AN
  • Rejestracja:prawie 19 lat
  • Ostatnio:7 minut
0
Dregorio napisał(a):

Pomijam fakt, że kod wygląda jak Java XD

Mogę bredzić, ale w Rust nie można myśleć jak w "obiektowce". Wartości w rust są przenoszone "moved" I nie mają "jednego miejsca".

Mam doświadczenie w C# i Java i obiektowy sposób programowania jest dla mnie czymś naturalnym.

elwis napisał(a):

Takie spaghetti, że się czytać nie chce. Funkcja addr1 to jest sposób, który bym zastosował, referencję rzutuję na wskaźnik, a potem na usize jak chcesz mieć taki typ… Uprość przypadek może…

W takim razie skróciłem i opisałem swój kod:

Kopiuj
// Struktura przechowująca dane
struct Book
{
    title: String,
}


impl Book
{
    // Pobranie adresu jako liczby - sposób 1
    // według https://stackoverflow.com/questions/35882994/is-there-any-way-to-get-the-address-of-a-struct-in-rust
    pub fn addr1(&self) -> usize {
        return (self as * const _) as usize;
    }

    // Pobranie adresu jako liczby - sposób 2
    pub fn addr2(&self) -> usize {
        return std::ptr::addr_of!(self) as usize;
    }
    
    // W Rust nie ma konstruktorów, ta funkcja jest substytutem konstruktora
    pub fn new(name: String) -> Self {
        let book: Book = Book {title: name};

        println!("* Tworzenie          {} {} {}", book.addr1(), book.addr2(), book.title);
        book
    }

    // Wypisanie adresu obiektu - oba sposoby
    pub fn printaddr(&self) {
        println!("  Adres              {} {} {}", self.addr1(), self.addr2(), self.title);
    }
}

// Implementacja destruktora obiektu według https://doc.rust-lang.org/reference/destructors.html
impl Drop for Book {
    fn drop(&mut self) {
        println!("# Niszczenie         {} {} {}", self.addr1(), self.addr2(), self.title);
    }
}


// Funkcja ze zmianą właściciela obiektu, wypisuje adres przekazanego obiektu
fn ownership_test(book: Book)
{
    println!("  przekazanie        {} {} {}", book.addr1(), book.addr2(), book.title);
}

// Funkcja z pożyczaniem obiektu, wypisuje adres przekazanego obiektu
fn borrow_test(book: &Book)
{
    println!("  Pozyczanie         {} {} {}", book.addr1(), book.addr2(), book.title);
}


fn main()
{
    let book1 = Book::new(String::from("Pierwszy"));
    let book2 = Book::new(String::from("Drugi"));

    book1.printaddr();
    book2.printaddr();
    
    borrow_test(&book1);

    book1.printaddr();

    ownership_test(book1);

    book2.printaddr();
}
Dregorio napisał(a):

W teoim wypadku natomiast jak odpalisz program w trybie release, a nie debug, to pewnie dostaniesz te same adresy. Ma to związek z optymalizacja robioną przez kompilator.

Pisząc to, zdałem sobie sprawę jak mało wiem o tym języku, w którym już długo pisze...

Wynik w trybie "debug":

Kopiuj
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.18s
* Tworzenie          140730438889488 140730438889440 Pierwszy
* Tworzenie          140730438889488 140730438889440 Drugi
  Adres              140730438889824 140730438889504 Pierwszy
  Adres              140730438889872 140730438889504 Drugi
  Pozyczanie         140730438889824 140730438889504 Pierwszy
  Adres              140730438889824 140730438889504 Pierwszy
  przekazanie        140730438889920 140730438889472 Pierwszy
# Niszczenie         140730438889920 140730438889136 Pierwszy
  Adres              140730438889872 140730438889504 Drugi
# Niszczenie         140730438889872 140730438889456 Drugi

Wynik w trybie "release":

Kopiuj
    Finished `release` profile [optimized] target(s) in 0.15s
* Tworzenie          140728200876032 140728200875936 Pierwszy
* Tworzenie          140728200876032 140728200875936 Drugi
  Adres              140728200876096 140728200875936 Pierwszy
  Adres              140728200876064 140728200875936 Drugi
  Pozyczanie         140728200876096 140728200875936 Pierwszy
  Adres              140728200876096 140728200875936 Pierwszy
  przekazanie        140728200876032 140728200875936 Pierwszy
# Niszczenie         140728200876032 140728200875936 Pierwszy
  Adres              140728200876064 140728200875936 Drugi
# Niszczenie         140728200876064 140728200875936 Drugi

Wniosek jest taki, że jak obiekt wchodzi lub wychodzi z funkcji z przekazaniem właściciela, czyli bez &, to zmienia adres podawany przez addr1, natomiast dwa obiekty mogą mieć ten sam adres podany funkcją addr2.

Czy dobrze rozumiem, że samo przekazywanie obiektu wraz ze zmianą właściciela powoduje przemieszczenie obiektu w pamięci, co skutkuje zmianą adresu wskaźnika?

loza_prowizoryczna
  • Rejestracja:ponad 2 lata
  • Ostatnio:około godziny
  • Postów:1597
2
andrzejlisek napisał(a):

Czy dobrze rozumiem, że samo przekazywanie obiektu wraz ze zmianą właściciela powoduje przemieszczenie obiektu w pamięci, co skutkuje zmianą adresu wskaźnika?

Dobrze rozumiesz. Adresy nie powinny być statyczne bo to ułatwia ich analizę i zwiększa powierzchnię ataku przez potencjalnego agresora. Do tego im więcej przemieszczeń w pamięci generowanych przez program tym trudniej wykonać zewnętrzny atak typu rowhammer przed którym nie znamy żadnego sensownego zabezpieczenia :(


Przetrzyma wszystko
elwis
  • Rejestracja:ponad 18 lat
  • Ostatnio:11 dni
1

No dokładnie tak jest, że w konstruktorze tworzysz obiekt, którego adres pobierasz, a potem przenosisz wraz ze zwróceniem wartości. Gdybyś chciał tego uniknąć, mógłbyś alokować na stercie, np używając Box<Book>.

Jeśli chodzi o funkcję addr2 to ona podaje adres w pamięci w którym znajduje się wskaźnik (parametr), dlatego skoro funkcje są wywoływane w tej samej funkcji to logiczne, że pierwszy parametr jest pod tym samym adresem. :) std::ptr::addr_of!(self) daje taki sam wynik jak&self as *const _. Tak więc to raczej musiałoby być std::ptr::addr_of!(*self).


edytowany 2x, ostatnio: elwis
AN
  • Rejestracja:prawie 19 lat
  • Ostatnio:7 minut
0

W takim razie, w jakiej postaci przekazywany jest obiekt do funkcji z przekazaniem właściciela?

Funkcje borrow_test i ownership_test wydają się być takie same za wyjątkiem faktu, że ta pierwsza tylko udostępnia book1 do skorzystania, a przy wywoływaniu ownership_test następuje podarowanie obiektu, że to funkcja ownership_test jest nowym właścicielem.

W przypadku borrow_test następuje przekazanie obiektu przez referencję (wskaźnik), więc normalne jest, że adres odczytany zarówno wewnątrz funkcji, jak i poza funkcją, jest taki sam.

Natomiast, przy wywołaniu ownership_test jest przekazanie parametru przez referencję, czy przez wartość? Przy tak małych strukturach to nie ma znaczenia wydajnościowego, ale przy gigantycznej macierzy może to mieć znaczenie. Czy można powiedzieć, że ownership_test przekazuje parametr jako wartość i stąd wynika inny adres? Jeżeli tak, to wtedy jest zrozumiałem, że w momencie wywołania ownership_test, patrząc od zewnątrz funkcji, zawartość pamięci struktury jest przekazywana jako parametr i ta pamięć jest uwalniana, a patrząc od wewnątrz funkcji, ona nie dostaje wskaźnika na strukturę, tylko dostaje zawartość struktury, więc musi na nowo zarezerwować pamięć, żeby gdzieś zachować dane z parametru i stąd jest nowy adres.

Całe zagadnienie bierze się z tego, że na początek nauki rusta chciałem mieć jakiś sposób, żeby móc śledzić istnienie obiektu od jego utworzenia do jego zniszczenia. W przypadku języka C++, każde tworzenie i każde niszczenie wykonuje programista i kompilator w ogóle nie kontroluje prawidłowości tych czynności, a także nie gwarantuje zniszczenia obiektu w przypadku zgubienia wskaźnika na obiekt. Wydawało się, że najprostszą rzeczą jest pozyskanie adresu w pamięci, zakładając, że podczas życia obiektu, nie ma niekontrolowanego uwolnienia i ponownego zarezerwowania pamięci tego obiektu.

hauleth
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:13 dni
2

Rust (podobnie z resztą jak Java) zawsze przekazuje przez wartość. Nie da się tego zmienić. Po prostu czasem przekazaną wartością jest referencja (podobnie jak w Javie). Zasadniczą różnicą między Javą a Rustem tutaj jest to, że w Ruscie to programista decyduje w jakiej formie jest to przekazane (oraz decyduje o ew. mutowalności przekazanej referencji).

więc musi na nowo zarezerwować pamięć, żeby gdzieś zachować dane z parametru i stąd jest nowy adres

Tak, ale nie do końca, bo kompilator jak najbardziej może usunąć tę kopię i przekazać obiekt "as is", bo ze względu na zasady ownership, mamy gwarancję, że nikt nie "zepsuje" nam tego obiektu jak jesteśmy jego właścicielem.

mieć jakiś sposób, żeby móc śledzić istnienie obiektu od jego utworzenia do jego zniszczenia

Zasadniczym pytaniem tutaj jest - po co? Jaki konkretnie problem chcesz tutaj rozwiązać.


loza_prowizoryczna
zawsze przekazuje przez wartość dinozaur jesteś. Niedługo wszyscy o tym zapomną i będzie pass by value i pass by reference jako rozróżnienie.
AN
  • Rejestracja:prawie 19 lat
  • Ostatnio:7 minut
0

Rust (podobnie z resztą jak Java) zawsze przekazuje przez wartość. Nie da się tego zmienić. Po prostu czasem przekazaną wartością jest referencja (podobnie jak w Javie).

To chyba jedno drugiemu zaprzecza. W przypadku Java, jak parametr jest wartością prymitywną, to zawsze przekazuje przez wartość (nawet nie da się w prosty sposób przekazać przez referencję, przeciwieństwie do C++ i C#), ale jak parametr jest typu jakiejś klasy bądź kolekcji, to parametr zawsze przekazywany jest przez referencję, bo przy wywołaniu funkcji nie jest tworzona kopia wartości, tylko operuje bezpośrednio na jednym i tym samym egzemplarzu obiektu. Także tutaj, nie da się zmienić, żeby przekazać obiekt przez wartość w sensie, że funkcja operuje na kopii instancji klasy.

Zasadniczą różnicą między Javą a Rustem tutaj jest to, że w Ruscie to programista decyduje w jakiej formie jest to przekazane (oraz decyduje o ew. mutowalności przekazanej referencji).

Rozumiem, że sposoby są następujące w przypadku struktur:

  1. Pożyczenie - przekazuje przez referencję.
  2. Oddanie własności - przekazuje przez wartość.
  3. Typy podstawowe zawsze przekazuje przez wartość.

Zasadniczym pytaniem tutaj jest - po co? Jaki konkretnie problem chcesz tutaj rozwiązać.

Problemem biznesowy, to uzyskanie unikatowego identyfikatora do celów edukacyjno-testowych. Już dawno stwierdziłem, że w odróżnieniu od Javy czy C++, nie ma możliwości tworzenia zmiennych globalnych, a więc w Rust nie da się zrobić czegoś takiego:

Kopiuj
int globalId = 0;

int getGlobalId()
{
    globalId++;
    return globalId;
}

class SomeClass
{
public:
    int id;
    SomeClass()
    {
        id = getGlobalId();
    }
};

int main(void)
{
    SomeClass obj1;
    SomeClass obj2;
    SomeClass * obj3 = new SomeClass();
    SomeClass * obj4 = new SomeClass();
    std::cout << obj1.id << std::endl;
    std::cout << obj2.id << std::endl;
    std::cout << obj3->id << std::endl;
    std::cout << obj4->id << std::endl;
    delete obj3;
    delete obj4;
    return 0;
}

Przyjmuję do wiadomości, że nie ma takich wskaźników w pamięci podobnych do tych w C++. Podobnie Java to w ogóle nie ma żadnych wskaźników. Pozostaje jedynie próba z obiektami statycznymi i singletonem. Potem pozostaje odpuścić, próby uzyskania adresu w pamięci na ogół i tak, nie są do niczego potrzebne.

edytowany 1x, ostatnio: andrzejlisek
LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:2 minuty
  • Postów:8399
2
andrzejlisek napisał(a):

problemem biznesowy, to uzyskanie unikatowego identyfikatora do celów edukacyjno-testowych. Już dawno stwierdziłem, że w odróżnieniu od Javy czy C++, nie ma możliwości tworzenia zmiennych globalnych, a więc w Rust nie da się zrobić czegoś takiego:

można zrobić static i będziesz miał zmienną globalną, jeśli naprawdę potrzebujesz (z tym, że mutowalne statiki to jest operacja unsafe). W przykładzie wyżej nie widzę, żeby była potrzeba. Pomijając już, że to wszystko w main jest (więc w tym konkretnym przypadku wystarczyłaby zmienna lokalna w main), to nawet wyobrażając sobie, że będzie z tego większa apka i obiekty tworzone będą w różnych miejscach, to dlaczego SomeClass ma w ogóle sam sobie ustawiać id? Moim zdaniem lepiej to opakować w fabrykę. Tylko pytanie - czy fabryka powinna mieć dostęp do tej zmiennej globalnej? Też niekoniecznie. Może zależy od stylu programowania, ale ja zwykle wolę zakładać, że wszystko jest w strukturach i nawet do tych "globalnych" rzeczy robię strukturę App albo coś podobnego np.

Kopiuj
struct App {
    last_id: usize
}

impl App {
    fn global_id(&mut self) -> usize {
        self.last_id += 1;
        self.last_id 
    }
}

#[derive(Debug)]
struct SomeStruct {
    id: usize
}

fn main() {
    println!("Hello, world!");
    let mut app = App { last_id: 0 };
    for i in 0..5 {
        let a = SomeStruct { id: app.global_id() };
        println!("{:?}", a);
    }

}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ab7403f11360cbf2823086f8e841e7e6
i do tego App można potem dodać dane "globalne" takie jak lista obiektów, funkcje-fabryki do ich tworzenia itp. Tak, żeby te dane "globalne" były jednak trzymane w jakiejś konkretnej strukturze.

No ale jednak dałoby się zrobić na static, mając zwykłe zmienne globalne:

Kopiuj
static mut last_id: usize = 0;

fn global_id() -> usize {
    unsafe {
        last_id += 1;
        last_id 
    }
}


#[derive(Debug)]
struct SomeStruct {
    id: usize
}

fn main() {
    println!("Hello, world!");
    for i in 0..5 {
        let a = SomeStruct { id: global_id() };
        println!("{:?}", a);
    }

}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b98f5b9d3a0fc88cf8dc04d0e3f9d0ad


edytowany 2x, ostatnio: LukeJL
LukeJL
@hauleth: o, ciekawe, nie używałem jeszcze atomic. Z drugiej strony ten boilerplate 🙈
hauleth
Okazało się, że niepotrzebnie. Niżej masz przykład już bez tego.
SL
  • Rejestracja:około 7 lat
  • Ostatnio:12 minut
  • Postów:866
2
andrzejlisek napisał(a):

To chyba jedno drugiemu zaprzecza. W przypadku Java, jak parametr jest wartością prymitywną, to zawsze przekazuje przez wartość (nawet nie da się w prosty sposób przekazać przez referencję, przeciwieństwie do C++ i C#), ale jak parametr jest typu jakiejś klasy bądź kolekcji, to parametr zawsze przekazywany jest przez referencję, bo przy wywołaniu funkcji nie jest tworzona kopia wartości, tylko operuje bezpośrednio na jednym i tym samym egzemplarzu obiektu. Także tutaj, nie da się zmienić, żeby przekazać obiekt przez wartość w sensie, że funkcja operuje na kopii instancji klasy.

Java nie ma referencji według klasycznej definicji. Javowe referencje na klasę to zwykłe wskaźniki z taką samą semantyką jak wskaźniki z C (można je nullować, można je przepisać do innej zmiennej, można je nadpisywać). Java po prostu nie pozwala na przekazanie obiektu przez wartość, bo język nie pozwala na dobranie się do obiektu inaczej niż przez referencję.

W takim C++ masz string i string*. Java nie pozwala na używanie typu string tylko masz String, który jest odpowiednikiem string* z Cpp. Samego String bez wskaźnika nie sposób doświadczyć, bo:

  • wszystkie operacje dzieją się przez referencję
  • nie ma semantyki kopiowania ani wyłuskania
  • obiekty robią się magicznie dzięki operatorowi new String, który zwraca referencję na obiekt

Podobnie Java to w ogóle nie ma żadnych wskaźników

Java to głównie wskażniki. Po prostu ludzie o tym tak nie myślą, bo nie ma arytmetyki jak i nie ma alternatywy w postaci wartości trzymanych w miejscu.

hauleth
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:13 dni
1

Z odrobiną samozaparcia (i głupoty) można coś takiego zrobić:

Kopiuj
use std::sync::atomic::{AtomicU64, Ordering};

static COUNTER: AtomicU64 = AtomicU64::new(0);

struct SomeStruct(u64);

impl SomeStruct {
  fn new() -> Self { SomeStruct(COUNTER.fetch_add(1, Ordering::Relaxed)) }
}

fn main() {
    let a = SomeStruct::new();
    let b = SomeStruct::new();
    let c = Box::new(SomeStruct::new());
    let d = Box::new(SomeStruct::new());

    println!("{}", a.0);
    println!("{}", b.0);
    println!("{}", c.0);
    println!("{}", d.0);
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d836ecba7099f4c0999943b88e7fd7a4

Jednakowoż użyteczność czegoś takiego (a zwłaszcza testowalność) jest IMHO znikoma.


edytowany 1x, ostatnio: hauleth
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)