Приведение ссылки на функцию с неверным указателем?

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).
Shepmaster
Это не совсем отвечает на ваш вопрос, но вы, вероятно, хотите:let ptr = foo as *const fn() as *const c_void;
Питер Холл

Ответы:

3

Этот ответ основан на ответах на сообщение об ошибке, мотивированных этим вопросом .

Каждая функция в Rust имеет свой отдельный тип элемента функции , который отличается от типа элемента функции любой другой функции. По этой причине экземпляру типа функционального элемента вообще не нужно хранить какую-либо информацию - на какую функцию он указывает, ясно из его типа. Таким образом, переменная х в

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которому, на мой взгляд, все еще гарантированно сохраняется идентичность указателя.
Трентл
2

На что делается _вывод и почему изменилось поведение?

Каждый раз, когда вы выполняете приведение необработанного указателя, вы можете изменить только один фрагмент информации (ссылочный или необработанный указатель; изменчивость; тип). Поэтому, если вы делаете это приведение:

let ptr = &foo as *const _

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

Вместо этого вы можете напрямую привести к указателю на функцию, которая выражается в синтаксисе Rust:

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

Что касается того, почему это изменилось, сказать сложно. Это может быть ошибка в ночной сборке. Стоит сообщить об этом - даже если это не ошибка, вы, вероятно, получите хорошее объяснение от команды компиляторов о том, что на самом деле происходит!

Питер Холл
источник
1
Спасибо, я сообщил об этом github.com/rust-lang/rust/issues/65499
Maciej Goszczycki
@MaciejGoszczycki Спасибо за сообщение! Ответы действительно прояснили для меня - я опубликую ответ на основе ответов там.
Свен Марнач