Что делает while (* p2 ++ = * p1 ++); имею в виду?

У меня есть блок кода:

int main ()
{
    char *p1 = "Hello";
    char *p2;
    p2 = (char*)malloc (20);
    memset (p2, 0, 20);
    while (*p2++ = *p1++);
    printf ("%s\n", p2);
}

Но я не могу объяснить работу линии while (* p2 ++ = * p1 ++); Не могли бы вы сообщить мне порядок работы в этой формуле?

Ответ 1

Этот классический C-код пытается выглядеть чрезвычайно умным, помещая все в одну строку.

while (*p2++ = *p1++); эквивалентно

strcpy(p2, p1);
p1 += strlen(p1) + 1;
p2 += strlen(p2) + 1;
Другими словами, он копирует строку с завершающим нулем, при этом p1 заканчивается, указывая на конец исходной строки, и p2 указывает на конец целевой строки.

Ответ 2

Это строковая копия, но вы теряете исходное значение указателя. Вы должны сохранить исходное значение указателя.

int main ()
{
    char *p1 = "Hello";
    char *p2 = malloc(20);
    char *p3 = p2;
    memset (p2, 0, 20);
    while (*p2++ = *p1++);
    printf ("%s\n", p3);
}

Фактическое семантическое объяснение цикла while будет примерно таким:

for (;;) {
    char *q2 = p2;              // original p2 in q2
    char *q1 = p1;              // original p1 in q1
    char c = *q1;               // original *p1 in c
    p2 += 1;                    // complete post increment of p2
    p1 += 1;                    // complete post increment of p1
    *q2 = c;                    // copy character *q1 into *q2
    if (c) continue;            // continue if c is not 0
    break;                      // otherwise loop ends
}

Порядок сохранения q1 и q2, а порядок, который p2 и p1 увеличиваются, могут быть изменены. Сохранение *q1 до c может произойти в любое время после сохранения q1. Назначение c - *q2 может возникать в любое время после сохранения c. На обратной стороне моего конверта это работает по меньшей мере на 40 различных интерпретаций.

Ответ 3

Цикл while оценивает выражение: *p2++ = *p1++. Выражение цикла while:
*p2 = *p1 оценивается с использованием результата *p1. Однако это значение по-прежнему присваивается *p2, даже если выражение оценивается как false или (0). Переписывая это:

char c;

do
{
    c = *p1; /* read the src byte */
    *p2 = c; /* write to dst byte */

    p2++, p1++; /* increment src, dst pointers */
}
while (c != 0);

Вы заметите, что чтение/запись произойдет хотя бы один раз. Это нормально, если строка C p1 имеет nul-terminated, а p2 имеет достаточно памяти для строки C. То есть malloc должен выделять не менее strlen(p1) + 1 байтов. В этом коде это верно.

Как отмечали другие, окончательная итерация будет оставить p1 по адресу one-past-the-end, который по-прежнему является действительным указателем, но имеет undefined результат при разыменовании. Адрес p2 является действительным указателем и действительным разыменованием, поскольку вы выделяете 20 байтов. Однако p2 больше не указывает на копию строки C. То, что вы хотите, эквивалентно:

char *p1 = "Hello";
char *p2, *tmp;

p2 = (char*)malloc (20);
memset (p2, 0, 20);

tmp = p2;
while (*tmp++ = *p1++);

printf ("%s\n", p2);

Большинство операционных систем освободят память на p2 при выходе из main, но рекомендуется отключить ресурсы с соответствующим вызовом:

free(p2);

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