Лиття посилання функції, що створює недійсний покажчик?


9

Я відслідковую помилку в коді сторонньої сторони, і я звузив її до чогось уздовж лінії.

use libc::c_void;

pub unsafe fn foo() {}

fn main() {
    let ptr = &foo as *const _ as *const c_void;
    println!("{:x}", ptr as usize);
}

Починаючи з стабільного 1,38.0, це друкує функцію вказівника, але бета (1,39,0-бета 6) та нічне повернення "1". ( Дитячий майданчик )

Про що робиться _висновок і чому поведінка змінилася?

Я припускаю, що правильний спосіб передавати це був би просто foo as *const c_void, але це не мій код.


Я не можу відповісти на "чому це змінилося", але я згоден з вами, що код починати неправильно. foo- це вже покажчик функції, тому не слід приймати до нього адресу. Це створює подвійну посилання, здавалося б, нульового типу (таким чином, магічне значення 1).
Шепмайстер

Це точно не відповідає на ваше запитання, але ви, мабуть, хочете:let ptr = foo as *const fn() as *const c_void;
Пітер Хол

Відповіді:


3

Ця відповідь ґрунтується на відповідях на повідомлення про помилку, мотивоване цим питанням .

Кожна функція в Rust має свій тип окремого функціонального елемента , який відрізняється від типу елемента функції кожної іншої функції. З цієї причини екземпляр типу функціонального елемента взагалі не потребує збереження інформації - на яку функцію він вказує, зрозуміло з його типу. Отже змінна x in

let x = foo;

є змінною розміру 0.

Типи елементів функції неявно примушують функціонувати типи вказівників, де це необхідно. Змінна

let x: fn() = foo;

є загальним вказівником на будь-яку функцію з підписом fn(), і тому потрібно зберігати вказівник на функцію, на яку вона фактично вказує, тому розмір x- це розмір вказівника.

Якщо ви приймаєте адресу функції, &fooви фактично приймаєте адресу нульового значення тимчасового значення. До цього приєднання до rustрепо , тимчасові нульові розміри використовували для створення виділення на стеку та &fooповертали адресу цього розподілу. Оскільки це робиться, нульові типи більше не створюють виділення, а замість цього використовують магічну адресу 1. Це пояснює різницю між різними версіями Rust.


Це має сенс, але я не переконаний, що це загально бажана поведінка, оскільки вона побудована на невпевненому припущенні. У безпечному коді Rust немає причин розрізняти покажчики на значення ZST - оскільки існує лише одне можливе значення, яке відомо під час компіляції. Це виходить з ладу, коли вам потрібно використовувати значення ZST поза системою типу Rust, наприклад, тут. Це, мабуть, стосується лише fnтипів предметів і неприхованих закриттів, і для тих, хто є вирішення, як у моїй відповіді, але це все ще досить пістолет!
Пітер Хол

Гаразд, я не читав новіші відповіді на питання Github. Я міг би отримати segfault з цим кодом, але, якщо код може викликати segfault, то я думаю, що нова поведінка в порядку.
Пітер Хол

Чудова відповідь. @PeterHall Я думав те саме, і я все ще не на 100% з цього приводу, але, принаймні, для тимчасових журналів та інших змінних стеків, не повинно виникнути проблем із встановленням усіх нульових значень на 0x1, оскільки компілятор не робить гарантії щодо компонування стека, і ви ніяк не можете гарантувати унікальність покажчиків на ZST. Це відрізняється від, скажімо, лиття, *const i32до *const c_voidякого, наскільки я розумію, все ще гарантується збереження ідентичності вказівника.
trentcl

2

Про що робиться _висновок і чому поведінка змінилася?

Кожен раз, коли ви робите неочищений вказівник, ви можете змінити лише одну частину інформації (посилання чи необроблений покажчик; змінність; тип). Тому, якщо ви робите цей склад:

let ptr = &foo as *const _

оскільки ви змінилися з посилання на необроблений покажчик, тип зробленого висновку _ повинен бути незмінним і, отже, є типом foo, який є деяким невиразним типом для функції foo.

Замість цього ви можете безпосередньо перейти до покажчика функції, який виражається в синтаксисі Руста:

let ptr = foo as *const fn() as *const c_void;

Щодо того, чому він змінився, важко сказати. Це може бути помилка в нічній збірці. Варто повідомити про це - навіть якщо це не помилка, ви, швидше за все, отримаєте гарне пояснення від команди-компілятора про те, що відбувається насправді!


1
Дякую, я повідомив про це github.com/rust-lang/rust/isissue/65499
Maciej Goszczycki

@MaciejGoszczycki Дякуємо за повідомлення! Відповіді насправді зрозуміли щось для мене - я опублікую відповідь на основі відповідей там.
Свен Марнахч
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.