C: Сравнение с NULL

Религиозные аргументы в стороне:

  • Опция 1:

    if (pointer[i] == NULL) ... 
  • Option2:

     if (!pointer[i]) ... 

В C опция 1 функционально эквивалентна опции2?

Неужели более поздние решения быстрее из-за отсутствия сравнения?

Мне нравится второй, другим людям нравится первый.

На самом деле, я предпочитаю третий вид:

 if (NULL == ptr) { ... } 

Потому что тогда я:

  • не смогут пропустить и просто наберите одно “=”
  • не пропустит «== NULL» и не ошибается, если условие длинное (несколько строк)

Функционально они эквивалентны.

Даже если указатель NULL не равен «0» (все нулевые биты), if (!ptr) сравнивается с указателем NULL .

Неправильно. Это все еще здесь, потому что есть много комментариев, ссылающихся на него: Не сравнивайте указатель с буквальным нолем. Он будет работать почти повсюду, но это неопределенное поведение IIRC.

Я предпочитаю явный стиль (первая версия). Это делает очевидным, что есть указатель, а не целое или что-то еще, но это всего лишь вопрос стиля.

С точки зрения производительности, это не должно иметь никакого значения.

Эквивалент. Он говорит так в стандарте языка. И у людей самые сущие религиозные предпочтения!

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

Если две формы эквивалентны, то почему компилятор просто не переводит один в другой, чтобы гарантировать, что обе они одинаково эффективны?

Если if (pointer[i] == NULL) был медленнее, чем if (!pointer[i]) , не компилятор просто изменил бы его на вторую, более эффективную форму?

Поэтому нет, если они эквивалентны, они одинаково эффективны.

Что касается первой части вопроса, то да, они эквивалентны. В языковом стандарте это явно указывается где-то – указатель оценивает значение true, если он не равен NULL, а false, если он NULL, поэтому они точно идентичны.

Почти наверняка никакой разницы в производительности. Тем не менее, я предпочитаю неявный стиль второго.

NULL должен быть объявлен в одном из стандартных файлов заголовков:

 #define NULL ((void*)0) 

Таким образом, в любом случае, вы сравниваете с нулем, и компилятор должен оптимизировать одинаково. Каждый процессор имеет некоторую «оптимизацию» или код операции для сравнения с нулем.

Ранняя оптимизация плохая. Микро-оптимизация также плохая, если вы не пытаетесь сжать каждый последний бит Гц от вашего процессора, нет смысла делать это. Как уже показали люди, компилятор в любом случае оптимизирует большую часть вашего кода.

Лучше всего сделать ваш код максимально понятным и понятным. Если это более читаемо

 if (!ptr) 

чем это

 if (NULL==ptr) 

затем используйте его. Пока все, кто будет читать ваш код, согласятся.

Лично я использую полностью определенное значение (NULL == ptr), поэтому ясно, что я проверяю. Может быть длиннее, но я могу легко прочитать его. Я бы подумал, что! Ptr будет легко пропустить! если вы читаете быстро.

Это действительно зависит от компилятора. Я был бы удивлен, если бы большинство современных компиляторов C не генерировали практически идентичный код для конкретного сценария, который вы описываете.

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

И даже если они отличаются, разница в производительности, вероятно, будет неактуальной в практических приложениях.

Включите оптимизацию компилятора, и они в основном одинаковы

протестировал это на gcc 4.3.3

 int main (int argc, char** argv) { char c = getchar(); int x = (c == 'x'); if(x == NULL) putchar('y'); return 0; } 

против

 int main (int argc, char** argv) { char c = getchar(); int x = (c == 'x'); if(!x) putchar('y'); return 0; } gcc -O -o test1 test1.c gcc -O -o test2 test2.c diff test1 test2 

не производил выход 🙂

Я сделал свалку сборки и нашел разницу между двумя версиями:

@@ -11,8 +11,7 @@
pushl %ecx
subl $20, %esp
movzbl -9(%ebp), %eax
- movsbl %al,%eax
- testl %eax, %eax
+ testb %al, %al

Похоже, последнее фактически генерирует одну инструкцию, а первое генерирует два, но это довольно ненаучно.

Это gcc, без оптимизации:

test1.c:

 #include  #include  int main(int argc, char *argv[]) { char *pointer[5]; if(pointer[0] == NULL) { exit(1); } exit(0); } 

test2.c: Изменить pointer[0] == NULL !pointer[0] pointer[0] == NULL to !pointer[0]

gcc -s test1.c, gcc -s test2.c, diff -u test1.s test2.s

 #include  #include  int main(int argc, char *argv[]) { char pointer[5]; /* This is insense you are comparing a pointer to a value */ if(pointer[0] == NULL) { exit(1); } ... } => ... movzbl 9(%ebp), %eax # your code compares a 1 byte value to a signed 4 bytes one movsbl %al,%eax # Will result in sign extension... testl %eax, %eax ... 

Остерегайтесь, gcc должен был выбить предупреждение, если не в случае компиляции с флагом -Wall на «Хотя», вы всегда должны компилировать оптимизированный код gcc. BTW, перед переменной введите ключевое слово volatile, чтобы избежать gcc игнорировать его …

Всегда упоминайте свою версию сборки компилятора 🙂