web-dev-qa-db-fra.com

Comment devez-vous faire l'arithmétique des pointeurs dans Rust?

Je sais que la réponse est "vous ne devriez pas" ... mais pour les besoins de l'argumentation, comment devriez vous le faites?

Par exemple, si vous vouliez écrire une alternative à Vec<T> Qui fonctionnait différemment.

Je vois que vous pouvez créer "quelque chose qui se compile et s'exécute" en transmutant les valeurs de * mut T En u64 Et en les ajoutant, puis en les transmutant de nouveau en * mut T Et en lisant la valeur à le pointeur (voir l'exemple ci-dessous). Cela semble fonctionner, mais cela laisse quelques questions ouvertes:

  1. Un pointeur * mut T S'insérera-t-il toujours dans u64?

  2. write() ing vers un pointeur non sûr déclenche-t-il des problèmes d'alias de pointeur lorsque les données sont un bloc de données arbitraire (c'est-à-dire pas un type géré) de libc:calloc?

  3. Cela ne fonctionne que parce que j'utilise un type primitif (f64). S'il s'agissait d'un véritable objet de données, je devrais forget() l'objet en premier; mais pouvez-vous simplement write() un * mut T dans une cible, puis heureusement read() le rééditer plus tard si le type est complexe et a des enregistrements enfants?

  4. Est-ce vraiment la bonne façon de procéder? Cela semble extrêmement maladroit. Je m'attendais à trouver une paire de ptrtoint()/inttoptr() dangereuse, mais je ne trouve rien de tel.

Exemple

extern crate libc;

use std::mem::size_of;
use std::ptr::write;
use std::ptr::read;
use std::mem::transmute;

use libc::calloc;
use libc::free;
use libc::c_void;

struct Array {
    length: usize,
    data: *mut f64,
}

impl Array {
    fn new(length: usize) -> Array {
        unsafe {
            Array {
                length: length,
                data: calloc(size_of::<f64>(), length) as *mut f64,
            }
        }
    }

    fn set(&mut self, offset: usize, value: f64) {
        if offset < self.length {
            unsafe {
                let root: *mut f64 = transmute(transmute::<*mut f64, u64>(self.data) +
                                               (size_of::<f64>() * offset) as u64);
                println!("Write: [{:?}] -> {}", root, value);
                write(root, value);
            }
        } else {
            println!("Write: Nope: [{}] is out of bounds", offset);
        }
    }

    fn get(&self, offset: usize) -> f64 {
        if offset < self.length {
            unsafe {
                let root: *const f64 = transmute(transmute::<*mut f64, u64>(self.data) +
                                                 (size_of::<f64>() * offset) as u64);
                let rtn = read::<f64>(root);
                println!("Read: [{:?}] -> {}", root, rtn);
                return rtn;
            }
        }
        println!("Read: Nope: [{}] is out of bounds", offset);
        0.0
    }
}

impl Drop for Array {
    fn drop(&mut self) {
        unsafe {
            free(self.data as *mut c_void);
        }
    }
}

fn main() {
    let mut tmp = Array::new(4);
    tmp.set(0, 100.5);
    tmp.set(1, 101.5);
    tmp.set(2, 102.5);
    tmp.set(3, 103.5);
    tmp.set(4, 104.5);
    tmp.get(0);
    tmp.get(1);
    tmp.get(2);
    tmp.get(3);
    tmp.get(4);
}

Production

Write: [0x7f04bdc1e080] -> 100.5
Write: [0x7f04bdc1e088] -> 101.5
Write: [0x7f04bdc1e090] -> 102.5
Write: [0x7f04bdc1e098] -> 103.5
Write: Nope: [4] is out of bounds
Read: [0x7f04bdc1e080] -> 100.5
Read: [0x7f04bdc1e088] -> 101.5
Read: [0x7f04bdc1e090] -> 102.5
Read: [0x7f04bdc1e098] -> 103.5
Read: Nope: [4] is out of bounds
38
Doug

Les pointeurs ont une méthode offset pour l'arithmétique des pointeurs.

fn main() {
    let items = [1usize, 2, 3, 4];

    let ptr = &items[1] as *const usize;

    println!("{}", unsafe { *ptr });
    println!("{}", unsafe { *ptr.offset(-1) });
    println!("{}", unsafe { *ptr.offset(1) });
}

Production

2
1
3

https://doc.Rust-lang.org/nightly/book/first-edition/unsafe.html

55
A.B.