Существует ли C эквивалентный указателю Rust NonNull <T>:: dangling ()? - PullRequest
2 голосов
/ 19 февраля 2020

Если он существует, он должен удовлетворять следующим свойствам:

  • Имеет тип void *
  • Не требует создания экземпляра "фиктивного объекта" для выполнения функции адрес
  • Гарантируется, что сравнение не будет равно NULL
  • Может быть сконструировано без вызова неопределенного поведения
  • Работает с компилятором, соответствующим стандартам, без использования нестандартных расширений

Сначала я подумал, что могу сделать что-то вроде (NULL + 1) или (void *)1, но это кажется проблематичным c. Первый использует указатель арифмети c на NULL, который, я считаю, является неопределенным поведением. Вторая основывается на том факте, что NULL не имеет физического адреса 1. (т.е. вполне возможно, что (void *)0 == (void *)1)

Ответы [ 2 ]

2 голосов
/ 19 февраля 2020

Любой указатель void удовлетворяет всем вашим требованиям.

Если вы точно знаете, какие адреса действительны и используются в указанной системе c, вы можете создать такой указатель вручную:

void* dangling = (void*)0x12345678; // an address which you know for sure isn't taken

Это полностью соответствует стандарту , Результат определяется реализацией, поскольку такие вещи, как выделенные действительные адреса и выравнивание, определяются системой c.

Что касается того, что это даст вам, я понятия не имею. Нулевые указатели - это то, что следует использовать, когда указатель не установлен для указания на выделенный адрес.


Сначала я подумал, что могу сделать что-то вроде (NULL + 1) или (void *) 1, но это выглядит проблематично c. Первый использует указатель арифмети c для NULL, который, я считаю, является неопределенным поведением.

Вы смешиваете нулевые указатели с константой нулевого указателя NULL. NULL может расширяться до 0 или (void*)0.

  • Если вы выполните арифметику c 0 + 1, вы просто получите целочисленное константное выражение 1. Который может быть преобразован в указатель, если вы хотите, то же поведение impl.defined, что и выше, и действительно эквивалентно (void*)1.
  • Если вы выполните арифметику c (void*)0 + 1, то код не будет компилироваться, так как Вы не можете делать арифметику c на пустых указателях. И это UB, если вы делаете арифметику указателя c на указатель, не указывающий на выделенный массив.
1 голос
/ 21 февраля 2020

NonNull::dangling() существует в Rust, чтобы иметь возможность временно инициализировать значение NonNull, прежде чем присвоить ему реальное значение. Вы не можете использовать null в качестве временного, потому что это NonNull, и оно будет отображать неопределенное поведение.

Например, этот совершенно безопасный (я полагаю) самоссылающийся пример требует NonNull::dangling():

struct SelfRef {
    myself: NonNull<SelfRef>,
    data: String,
}

impl SelfRef {
    fn new(data: String) -> Pin<Box<SelfRef>> {
        let mut x = Box::pin(SelfRef {
            myself: NonNull::dangling(),
            data,
        });
        x.myself = unsafe { NonNull::new_unchecked(x.as_mut().get_unchecked_mut()) };
        x
    }
}

Ваш вопрос об эквиваленте NonNull::dangling() в C заключается в том, что в C нет NonNull, поэтому для этих видов временной инициализации вы можете NULL или просто оставьте его унитализованным, пока не получите правильное значение.

struct SelfRef {
    SelfRef *myself;
    //...
};

struct SelfRef *new_selfref() {
    struct SelfRef *x = malloc(sizeof(struct SelfRef));
    //Here x->myself is uninitialized, that is as good as dangling()
    x->myself = x;
    return x;
}

Тем не менее, я уверен, что есть и другие варианты использования NonNull::dangling, кроме временной инициализации самоссылающихся структур. Для тех, кто вам может понадобиться эквивалентный C код. Эквивалентный код C был бы (в виде макроса, поскольку он принимает тип в качестве аргумента):

#define DANGLING(T) ((T*)alignof(T))

То есть указатель должен быть как можно ближе к нулю при соблюдении выравнивания данного типа , Идея состоит в том, что в большинстве архитектур указатель NULL фактически находится по адресу 0, и первые несколько килобайт никогда не отображаются, так что среда выполнения может отлавливать разыменования NULL. А поскольку максимальные требования к выравниванию обычно составляют всего несколько байтов, это никогда не будет указывать на допустимую память.

...