Могут ли memcpy или memmove вернуть указатель, отличный от указателя dest?

Функция memmove определяется следующим образом:

void *memmove(void *dest, const void *src, size_t n);

На странице руководства Linux написано:

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
Функция memmove() возвращает указатель на пункт назначения.

Почему функция не определена просто как void memmove(…), если она всегда возвращает один из входных параметров? Может ли возвращаемое значение отличаться от dest?

Или действительно всегда возвращаемое значение dest, и это делается только для того, чтобы иметь возможность творчески скомпоновать функцию?


person Martin Ueding    schedule 12.10.2016    source источник
comment
Отлично, мне придется отложить свой обед, чтобы увидеть ответ на этот вопрос... :) Я подозреваю, что последнее ваше предложение имеет место быть.   -  person gsamaras    schedule 12.10.2016
comment
... вернуть указатель, отличный от адресата? --› С UB все возможно, иначе нет.   -  person chux - Reinstate Monica    schedule 12.10.2016
comment
Возможный дубликат: stackoverflow.com/ вопросы/13720428/   -  person P.P    schedule 12.10.2016


Ответы (3)


memmove никогда не вернет ничего, кроме dest.

Возврат dest, в отличие от создания memmove недействительным, полезен, когда первый аргумент является вычисляемым выражением, потому что это позволяет избежать вычисления одного и того же значения заранее и сохранения его в переменной. Это позволяет вам делать в одной строке

void *dest = memmove(&buf[offset] + copiedSoFar, src + offset, sizeof(buf)-offset-copiedSoFar);

что вам в противном случае нужно было бы сделать в двух строках:

void *dest = &buf[offset] + copiedSoFar;
memmove(dest, src + offset, sizeof(buf)-offset-copiedSoFar);
person Sergey Kalinichenko    schedule 12.10.2016
comment
@usr, но это причина, по которой большинство строковых функций и функций памяти возвращают конечный указатель, или что вы можете сделать a = b = c = d. Возврат значения очень дешев, а BTW void — это новое изобретение. В оригинальном K&R C не было void. - person Giacomo Catenazzi; 12.10.2016
comment
Заставьте его ничего не возвращать. Это не забота этой функции. Строковые функции возвращают указатель, потому что указатель вычисляется способом O(N) (если я ошибаюсь в том, что строковые функции также не должны возвращать указатель). - person usr; 12.10.2016
comment
@usr: для таких функций, как strcat, имело бы смысл возвращать указатель на конец адресата, но они этого не делают. Вместо этого они возвращают указатель на начало строки. - person supercat; 13.10.2016
comment
Это довольно слабый аргумент, имхо. Если бы вы показали мой первый пример в обзоре кода, я бы попросил вас превратить его во второй. Почему работа memmove заключается в том, чтобы помогать вам делать остроты? Гораздо лучшим возвращаемым значением было бы dest + n, потому что тогда вы могли бы связать все вместе: dest = memcpy(dest, ...); dest = memcpy(dest.. ); etc. - person GManNickG; 13.10.2016
comment
@GManNickG Для меня большая часть красоты C заключается в его удивительных остротах. Мой личный фаворит — while ((*dest++ = *src++)); для копирования строк. Как только я увидел точку с запятой в конце, меня зацепило! Я согласен с вами, что возврат конца буфера, а не его начала, имел бы гораздо больше смысла. - person Sergey Kalinichenko; 13.10.2016
comment
@dasblinkenlight Я бы назвал это неясным, а не удивительным, по крайней мере, для всех, кроме программистов на C. Если я хочу скопировать строки в одну строку, я бы все равно использовал strcpy. - person Malcolm; 13.10.2016
comment
Однострочники в C очаровательны и приятны, но их следует рассматривать как искусство, а не код, достойный производства. - person usr; 13.10.2016
comment
@usr Я думаю, что общая философия заключается в том, что возвращать что-то лучше, чем ничего не возвращать. Даже если возвращаемое значение избыточно, могут быть ситуации, когда оно полезно, например, связанные вызовы в других ответах. Зачем исключать такие вещи? - person Barmar; 18.10.2016
comment
@Barmar, потому что это затрудняет понимание API при чрезвычайно низкой добавленной стоимости. Он добавляет в API совершенно не связанные вычисления. Это усложняет обучение. Если возвращаемое значение действительно используется, код теперь сложнее понять и проверить. - person usr; 18.10.2016
comment
Ожидается, что программисты @usr C будут достаточно умными (предполагается, что они смогут избежать переполнения массива без какой-либо автоматической проверки). Дизайнер, по-видимому, считал, что эта функция имеет большую ценность, чем вы. Вы имеете право на свое мнение, но это не делает его неправильным. - person Barmar; 18.10.2016
comment
@Barmar, кого волнует, что от них ожидают. Это снижает производительность без какой-либо другой выгоды и, следовательно, является объективно неправильным. Я понимаю, что дизайнер со мной не согласен. Иначе зачем бы он это сделал?! Многие C API ужасно плохо спроектированы. В то время программисты были менее опытны в разработке API. - person usr; 18.10.2016
comment
@usr Вы сказали, что это запутанный API. Если это слишком запутывает программиста, ему не следует использовать C, потому что в нем есть гораздо более запутанные вещи, такие как все детали, о которых вам нужно беспокоиться, чтобы избежать переполнения буфера. - person Barmar; 18.10.2016
comment
@Barmar Я согласен с GManNickG и usr. Это действительно слабый аргумент. Эта функция не имеет смысла. Когда C жертвует удобочитаемостью и затрудняет понимание новичками, у него обычно есть более веская причина, чем создание крутых острот. Это не задача API, и программисты на C не должны быть обременены произвольной сложностью только потому, что от нас этого ожидают. Мне было бы более интересно узнать, есть ли историческая причина для этого API. - person PC Luddite; 25.10.2016
comment
@Малькольм, да, но memmove может пересекаться :O - person GuillemVS; 30.12.2020
comment
@GuillemVS Дело было не в этом. - person Malcolm; 31.12.2020

Согласно C11, глава §7.24.2.1 и §7.24.2.2

void *memcpy(void * restrict s1, const void * restrict s2, size_t n);

[...] Функция memcpy возвращает значение s1.

и,

void *memmove(void *s1, const void *s2, size_t n);

[...] Функция memmove возвращает значение s1.

Таким образом, функции всегда будут возвращать указатель на буфер назначения, так задумано.

Теперь перейдем к части почему. Многие функции спроектированы таким образом, чтобы сделать возможной цепочку вызовов функций. Таким образом, вы можете вызвать memmove() в качестве аргумента другой функции, где скопированное значение (т. е. указатель на dest) пригодится.

Например, вы можете написать более короткий

 puts(memmove(dest_buffer, src_buffer, welcome_message_size));

вместо более длинного

 memmove(dest_buffer, src_buffer, welcome_message_size);
 puts(dest_buffer);
person Sourav Ghosh    schedule 12.10.2016

Идиома возврата точного значения одного из аргументов (типа указателя) существует для поддержки "связанных" вызовов функций (см. также strcpy, strcat и т. д.). Это позволяет вам писать повторяющийся код в виде одного оператора выражения вместо того, чтобы разбивать его на несколько операторов. Например

char buffer[1024];
printf("%s\n", strcat(strcat(strcpy(buffer, "Hello"), " "), "World"));

struct UserData data_copy;
some_function(memcpy(&data_copy, &original_data, sizeof original_data));

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

Можно даже сказать, что значение этой идиомы немного возросло после введения составных литералов в C99. С составными литералами именно эта идиома позволяет писать тот же код, не вводя именованную промежуточную переменную.

printf("%s\n", strcat(strcat(strcpy((char [1024]) { 0 }, "Hello"), " "), "World!"));

some_function(memcpy(&(struct UserData) { 0 }, &original_data, sizeof original_data));

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

person AnT    schedule 12.10.2016
comment
Я мало работаю с C, но разве sizeof data_copy в вашем примере с составными литералами не должно быть sizeof original_data, поскольку data_copy больше не существует? - person Pokechu22; 12.10.2016
comment
Конечно, пример со strcat будет блестяще неэффективен, поскольку каждая операция strcat будет начинаться со сканирования места назначения для поиска нулевого байта, затем конкатенировать новые данные, а затем возвращать начало места назначения, поэтому следующий вызов должен будет просмотреть как старые, так и новые данные, чтобы найти новое местоположение нулевого байта. - person supercat; 13.10.2016
comment
@supercat: Да, наверное, можно с уверенностью сказать, что strcat — бесполезная функция. Он используется в моем ответе исключительно в иллюстративных целях. - person AnT; 13.10.2016