Ответ 1
Комментарий Дэвида Хеффернана правильный. Этот код ужасен.
Точка кода, о которой вы спрашиваете, это пропустить разыменование q
, если оно равно null. Поэтому автор кода считает, что q
может быть нулевым. При каких обстоятельствах q
может быть нулевым? Наиболее очевидным является: if p
null.
Итак, посмотрим, что делает код, когда p
имеет значение null.
void strrev(char *p) // Assumption: p is null
{
char *q = p; // now q is null
while(q && *q) ++q; // The loop is skipped, so q and p are both still null.
for(--q;
Итак, первое, что мы делаем, это декремент q, который является нулевым. Вероятно, это обернется, и мы получим в результате q, содержащий максимально возможный указатель.
p < q;
Поскольку значение null меньше, чем все, кроме null, а q больше не равно null, это верно. Мы вводим цикл...
++p, --q)
*p = *p ^ *q,
И быстро разыщите нулевое значение.
*q = *p ^ *q,
*p = *p ^ *q;
}
Кстати, в Coverity мы называем это "прямым нулевым дефектом", то есть шаблоном, в котором путь кода указывает, что значение, как ожидается, будет потенциально нулевым, а затем на том же пути кода предполагает, что он не равен нулю. Это чрезвычайно распространено.
ОК, поэтому этот код полностью сломан, если в качестве аргумента задано значение null. Есть ли другие способы, чтобы это было нарушено? Что произойдет, если мы дадим ему пустую строку?
void strrev(char *p) // Assumption: *p is 0
{
char *q = p; // *q is 0
while(q && *q) ++q; // The second condition is not met so the body is skipped.
for(--q; // q now points *before valid memory*.
p < q // And we compare an invalid pointer to a valid one.
Есть ли у нас гарантия на C, которая говорит, что когда вы вычитаете один из указателя на действительную память и затем сравниваете этот указатель на другой указатель, чтобы сравнение было разумным? Потому что это кажется мне невероятно опасным. Я не очень хорошо знаю стандарт C, чтобы сказать, является ли это undefined поведением или нет.
Кроме того, этот код использует ужасный "своп двух символов с xor". Зачем это делать?. Он генерирует более крупный, медленный машинный код, и его труднее читать, понимать и поддерживать. Если вы хотите обменять две вещи, заменить их.
Он также использует оператор запятой, чтобы поместить несколько операторов в один оператор, чтобы избежать ужаса скобок вокруг тела for
. Какова цель этой странности? Цель кода - не показывать, сколько операторов вы знаете, это прежде всего общение с читателем кода.
Функция также изменяет свой формальный параметр, что затрудняет отладку.