Lançando uma referência de função produzindo um ponteiro inválido?

9

Estou rastreando um erro no código de terceiros e o reduzi a algo ao longo das linhas de.

use libc::c_void;

pub unsafe fn foo() {}

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

Executado no estável 1.38.0, imprime o ponteiro da função, mas beta (1.39.0-beta.6) e o retorno noturno '1'. ( Parque infantil )

O que é _inferido e por que o comportamento mudou?

Presumo que a maneira correta de transmitir isso seria simplesmente foo as *const c_void, mas esse não é o meu código.

Maciej Goszczycki
fonte
Não consigo responder "por que mudou", mas concordo que o código está incorreto para começar. foojá é um ponteiro de função, portanto, você não deve levar um endereço para ele. Isso cria uma referência dupla, aparentemente para um tipo de tamanho zero (portanto, o valor mágico 1).
Shepmaster 16/10/19
Isso não responder exatamente a sua pergunta, mas você provavelmente vai querer:let ptr = foo as *const fn() as *const c_void;
Peter Hall

Respostas:

3

Esta resposta é baseada nas respostas do relatório de erros motivadas por esta pergunta .

Cada função no Rust possui seu tipo de item de função individual , que é distinto do tipo de item de função de todas as outras funções. Por esse motivo, uma instância do tipo de item de função não precisa armazenar nenhuma informação - para qual função ela aponta é clara em seu tipo. Então a variável x em

let x = foo;

é uma variável de tamanho 0.

Os tipos de itens de função coagem implicitamente para tipos de ponteiros de função, quando necessário. A variável

let x: fn() = foo;

é um ponteiro genérico para qualquer função com assinatura fn()e, portanto, precisa armazenar um ponteiro na função para a qual ele realmente aponta; portanto, o tamanho de xé o tamanho de um ponteiro.

Se você pegar o endereço de uma função, &foona verdade, está usando o endereço de um valor temporário de tamanho zero. Antes de este se comprometer com a rustrepo , temporários de tamanho zero usado para criar uma alocação na pilha, e &foodevolveu o endereço dessa alocação. Desde essa confirmação, os tipos de tamanho zero não criam mais alocações e, em vez disso, usam o endereço mágico 1. Isso explica a diferença entre as diferentes versões do Rust.

Sven Marnach
fonte
Isso faz sentido, mas não estou convencido de que geralmente seja um comportamento desejado, porque é construído sobre uma suposição precária. No código Rust seguro, não há razão para distinguir ponteiros para um valor de um ZST - porque existe apenas um valor possivelmente conhecido em tempo de compilação. Isso é interrompido quando você precisa usar um valor ZST fora do sistema do tipo Rust, como aqui. Provavelmente, afeta apenas os fntipos de itens e os fechamentos que não capturam e, para aqueles, existe uma solução alternativa, como na minha resposta, mas ainda é uma arma de fogo!
27519 Peter Hall
Ok, eu não tinha lido as respostas mais recentes sobre a questão do Github. Eu poderia obter um segfault com esse código, mas, se o código poderia causar um segfault, acho que o novo comportamento está ok.
Peter Hall
Ótima resposta. @ PeterHall Eu estava pensando a mesma coisa, e ainda não estou 100% no assunto, mas pelo menos para temporários e outras variáveis ​​de pilha, não deve haver problema em colocar todos os valores de tamanho zero em 0x1 porque o compilador não faz garante o layout da pilha e você não pode garantir a exclusividade dos ponteiros para os ZSTs. Isso é diferente de, digamos, lançar um *const i32para o *const c_voidqual, no meu entender, ainda é garantido preservar a identidade do ponteiro.
trentcl
2

O que é _inferido e por que o comportamento mudou?

Cada vez que você faz uma conversão de ponteiro bruto, você pode alterar apenas uma informação (referência ou ponteiro bruto; mutabilidade; tipo). Portanto, se você fizer este elenco:

let ptr = &foo as *const _

desde que você mudou de uma referência para um ponteiro bruto, o tipo inferido _ deve permanecer inalterado e, portanto, é o tipo de foo, que é um tipo inexprimível para a função foo.

Em vez de fazer isso, você pode converter diretamente em um ponteiro de função, expressável na sintaxe Rust:

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

Quanto à razão pela qual mudou, é difícil dizer. Pode ser um bug na compilação noturna. Vale a pena denunciá-lo - mesmo que não seja um bug, você provavelmente obterá uma boa explicação da equipe do compilador sobre o que realmente está acontecendo!

Peter Hall
fonte
11
Obrigado, eu relatei isso github.com/rust-lang/rust/issues/65499 #
65699 Maciej Goszczycki
@MaciejGoszczycki Obrigado por denunciar! As respostas realmente esclareceram as coisas para mim - postarei uma resposta com base nas respostas.
Sven Marnach 17/10/19