Как я могу проверить поведение своего кода при получении указателя null-но-не-0 и указателя 0-но-не-null?

#c #testing #null #compiler-optimization #zero

Вопрос:

Я хочу адаптировать фрагмент кода для работы в системах, где нулевой указатель не равен 0 в своем физическом представлении. До сих пор в жизни я полностью игнорировал различие между нулем и 0 — и до сих пор сегодня я не работаю ни над одной системой, которая имеет это различие на практике, — но все же.

Итак, я пошел и снова прочитал часто задаваемые вопросы по C о нулях, и особенно 5.19, о том, как получить указатель «фактического нуля». Ну, я хочу сделать это в модульном тесте, который я пишу, но — я беспокоюсь, что некоторые из этих «трюков» на самом деле будут оптимизированы разумным компилятором.

Например, используя memset() и `memcmp(): И GCC, и Clang видят это насквозь, так что это:

 #include <stdio.h>
#include <string.h>

int foo(void* p)
{
    void* ref;
    memset(amp;ref, 0, sizeof(ref));
    return (memcmp(amp;p, amp;ref, sizeof(p)) == 0 ) ? 123: 456000;
}

int bar(void* p)
{
    memset(amp;p,  0, sizeof(p));
    return (p == 0) ? 123: 456000;
}
 

становится, скажем:

 foo:                                    # @foo
        test    rdi, rdi
        mov     ecx, 123
        mov     eax, 456000
        cmove   eax, ecx
        ret
bar:                                    # @bar
        mov     eax, 123
        ret
 

Итак, кроме компиляции без оптимизации, как я могу надежно «принудительно обнулить» указатель?

Примечание: Спасибо @Lundin за указание на то, что, если я использую memset() , я также, вероятно, захочу использовать memcmp() ).

Комментарии:

1. memset_s ? Это единственный стандартный способ, несмотря на то, что он необязателен. godbolt.org/z/evW7Tb3f8

2. @Рассказчик-UnslanderMonica: Интересный вариант. Вероятно, тоже стоит ответа. Однако… существует хорошая корреляция между наличием ненулевого нуля и отсутствием поддержки C11 🙁

3. bar Оптимизированный код asm логичен, как p и локальный аргумент bar , и вы ничего не делаете с ним, кроме как устанавливаете его равным нулю и тестируете его против нуля. Может быть, вы сможете получить более значимые результаты, если вернетесь p и назначите bar возврат вызывающей локальной/статической переменной ?

4. Вы можете вызвать внешнюю функцию-оболочку, чтобы предотвратить memset оптимизацию вызова.

5. Он оптимизируется, потому что компилятор использует ноль в качестве представления нулевого указателя… это правильная и четко определенная оптимизация.

Ответ №1:

memset это правильное решение. И поскольку вы компилируете с компиляторами, которые используют ноль в качестве реализации нулевого указателя, может быть довольно сложно заблокировать оптимизацию.

Однако у тебя есть ошибка.

  • Проверка указателя, например, if(p) проверяет, является ли этот указатель нулевым указателем.
  • Сравнение p == 0 p == (void*)0 или p == NULL проверяет , является ли этот указатель нулевым указателем.

Поэтому p == 0 не проверяет, равно ли внутреннее представление нулю. Вам придется использовать что-то вроде:

 void* ref;
memset(amp;ref, 0, sizeof ref);

if(memcmp(amp;p, amp;ref, sizeof p)==0)
 

Что опять же, вероятно, не приведет ни к чему революционному в системе, использующей ноль для представления нулевого указателя.

Комментарии:

1. Я изменяю рассматриваемый код, чтобы использовать memcmp (), поскольку в этом случае также происходит оптимизация, и вопрос касался не моего примера кода, а общей проблемы.

2. @einpoklum Да, но memset-это правильный способ, и оптимизация правильна, так как компилятор, который вы использовали, использует нули для представления нулевого указателя. Чтобы извлечь из этого что-то еще, вам нужно использовать перекрестный компилятор для системы, которая не использует нули для нулевого указателя. В этом случае оптимизация была бы неправильной.

3. И в этой экзотической системе, которая использует, скажем, 0xFFFF для представления нулевого указателя, затем p = 0; установит p значение 0xFFFF.

4. Я предположил p == 0 , что проверка OP была преднамеренной. Т. е. она проверяет, является ли указатель нулевым после установки его байтового представления для всех нулей. Интересно порассуждать о том, что p == 0 даст для всех нулей ненулевое значение указателя. Я предполагаю, что результат должен быть равен 0 для согласованности, если только реализация не имеет нескольких представлений null, одно из которых-все нули.