Значение strcpy () return

Многие функции из стандартной библиотеки C, особенно те, которые используются для обработки строк, и, в частности, strcpy (), используют следующий прототип:

char *the_function (char *destination, ...) 

Возвращаемое значение этих функций на самом деле такое же, как и указанное destination . Зачем вам тратить обратную стоимость на что-то избыточное? Это означает, что такая функция недействительна или возвращает что-то полезное.

Мое единственное предположение о том, почему это так, проще и удобнее вставить вызов функции в другое выражение, например:

 printf("%s\n", strcpy(dst, src)); 

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

как отметил Эван, можно сделать что-то вроде

 char* s = strcpy(malloc(10), "test"); 

например, присваивать значение malloc()ed memory без использования вспомогательной переменной.

(этот пример не самый лучший, он выйдет из состояния нехватки памяти, но идея очевидна)

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

Его также очень легко кодировать.

Возвращаемое значение обычно остается в регистре AX (это необязательно, но это часто бывает). И пункт назначения помещается в регистр AX при запуске функции. Чтобы вернуть место назначения, программисту нужно делать … точно ничего! Просто оставьте значение, где оно находится.

Программист может объявить функцию void . Но это возвращаемое значение уже в нужном месте, просто ожидая возвращения, и даже не требует дополнительной инструкции, чтобы вернуть его! Независимо от того, насколько небольшое улучшение, в некоторых случаях это удобно.

char *stpcpy(char *dest, const char *src); возвращает указатель на конец строки и является частью POSIX.1-2008 . До этого это было расширение GNU libc с 1992 года. Если впервые появилось в Lattice C AmigaDOS в 1986 году.

gcc -O3 в некоторых случаях оптимизирует strcpy + strcat для использования stpcpy или strlen + встроенного копирования, см. ниже.


Стандартная библиотека C была разработана очень рано, и очень легко утверждать, что функции str* не оптимально разработаны. Функции ввода-вывода определенно были разработаны очень рано, в 1972 году, прежде чем C даже имел препроцессор, поэтому fopen(3) принимает строку режима вместо фрейма bitmap как Unix open(2) .

Мне не удалось найти список функций, включенных в «переносимый пакет ввода-вывода» Майка Леска, поэтому я не знаю, будет ли в данный момент даты в текущей форме, или если эти функции были добавлены позже , (Единственный реальный источник, который я нашел, – это широко известная статья C истории Dennis Ritchie , которая отличная, но не настолько глубокая. Я не нашел никакой документации или исходного кода для самого пакета ввода-вывода.)

Они появляются в их нынешнем виде в первом выпуске K & R 1978 года.


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

Как @R говорит:

Мы все хотим, чтобы эти функции вернули указатель на завершающий нулевой байт (что уменьшило бы много операций O(n) до O(1) )

например, вызов strcat(bigstr, newstr[i]) в цикле для создания длинной строки из многих коротких (O (1)) строк имеет приблизительно O(n^2) сложность, но strlen / memcpy будет смотреть только на каждый символ дважды (один раз в strlen, один раз в memcpy).

Используя только стандартную библиотеку ANSI C, нет возможности эффективно просматривать каждый символ только один раз . Вы можете вручную написать цикл byte-at-time, но для строк длиной более нескольких байтов это хуже, чем просмотр каждого символа дважды с помощью текущих компиляторов (которые не будут автоматически векторизовать цикл поиска) на современном HW, с использованием эффективных libc-предоставленных SIMD-строк и memcpy. Вы можете использовать length = sprintf(bigstr, "%s", newstr[i]); bigstr+=length; length = sprintf(bigstr, "%s", newstr[i]); bigstr+=length; , но sprintf() должен анализировать свою строку формата и не быстро.

Существует даже версия strcmp или memcmp которая возвращает позицию разницы . Если это то, что вы хотите, у вас такая же проблема, как и почему так быстро выполняется сравнение строк в python? : оптимизированная функция библиотеки, которая работает быстрее, чем все, что вы можете сделать с скомпилированным циклом (если у вас нет оптимизированного вручную asm для каждой целевой платформы, о которой вы заботитесь), которую вы можете использовать, чтобы приблизиться к разному байту, прежде чем вернуться к регулярный цикл, как только вы приблизитесь.

Похоже, что строковая библиотека C была спроектирована независимо от стоимости O (n) любой операции, а не только для того, чтобы найти конец строк с неявной длиной, а поведение strcpy определенно не является единственным примером.

Они в основном обрабатывают строки неявной длины как целые непрозрачные объекты, всегда возвращая указатели на начало, никогда до конца или в позицию внутри одного после поиска или добавления.


Догадка истории

В начале C на PDP-11 я подозреваю, что strcpy был более эффективным, чем while(*dst++ = *src++) {} (и, вероятно, был реализован именно так).

Фактически, первое издание K & R (стр. 101) показывает, что реализация strcpy и говорит:

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

Это подразумевает, что они полностью ожидали, что программисты будут писать свои собственные циклы в тех случаях, когда вы хотите получить окончательное значение dst или src . И, возможно, они не видели необходимости перепроектировать стандартный библиотечный API до тех пор, пока не стало слишком поздно раскрывать более полезные API для оптимизированных вручную функций библиотеки asm.


Но возвращает ли первоначальное значение dst какой-либо смысл?

strcpy(dst, src) возвращающий dst , аналогичен x=y вычисляющему x . Поэтому он делает работу strcpy как оператор присваивания строк.

Как указывают другие ответы, это позволяет вложенность, например foo( strcpy(buf,input) ); , Ранние компьютеры были очень ограничены памятью. Сохранение компакт-диска с исходным кодом было обычной практикой . Вероятно, перфокарт и медленные терминалы были фактором. Я не знаю исторических стандартов кодирования или руководств по стилю или того, что считалось слишком большим, чтобы поставить на одну строку.

Яркими старыми компиляторами также может быть фактор. С современными оптимизирующими компиляторами char *tmp = foo(); / bar(tmp); не медленнее, чем bar(foo()); , но это с gcc -O0 . Я не знаю, могли ли очень ранние компиляторы полностью оптимизировать переменные (не оставляя для них пространства стека), но, надеюсь, они могли бы по крайней мере держать их в регистрах в простых случаях (в отличие от современного gcc -O0 который специально проливает / перезагружает все для последовательная отладка). то есть gcc -O0 не является хорошей моделью для древних компиляторов, потому что это анти-оптимизация для последовательной отладки.


Возможная мотивация, генерируемая компилятором-asm

Учитывая отсутствие заботы об эффективности в общем дизайне API библиотеки строк C, это может быть маловероятным. Но, возможно, было преимущество в размере кода. (На ранних компьютерах размер кода был более жестким, чем процессорное время).

Я не очень разбираюсь в качестве ранних компиляторов C, но это безопасная ставка, что они не были удивительными при оптимизации, даже для хорошей простой / ортогональной архитектуры, такой как PDP-11.

Обычно вызов строки после вызова функции. На уровне asm вы (компилятор), вероятно, имеете его в регистре перед вызовом. В зависимости от соглашения о вызове вы либо нажимаете его на стек, либо копируете его в правый регистр, где соглашение о вызове говорит, что первый arg идет. (т. е. где strcpy ожидает этого). Или, если вы планируете заранее, у вас уже есть указатель в правом регистре для вызывающего соглашения.

Но вызовы функций clobber некоторые регистры, включая все arg-проходящие регистры. (Поэтому, когда функция получает arg в регистре, она может увеличивать ее там вместо копирования на регистр нуля).

Так как вызывающий пользователь, ваш код-ген вариант для хранения чего-то через вызов функции include в себя:

  • хранить / перезагружать его в локальную память стека. (Или просто перезагрузите его, если в памяти по-прежнему находится обновленная копия).
  • сохранять / восстанавливать регистр, сохраняемый при вызове, в начале / конце всей функции и копировать указатель на один из этих регистров перед вызовом функции.
  • функция возвращает значение в регистре для вас. (Конечно, это работает только в том случае, если источник C записывается для использования возвращаемого значения вместо входной переменной. Например, dst = strcpy(dst, src); если вы не вложите его).

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

Вероятно, вы получили лучший asm от примитивных ранних компиляторов C, используя возвращаемое значение strcpy (уже в регистре), чем заставив компилятор сохранить указатель вокруг вызова в регистре, защищенном вызовом, или вылить его в стек. Это может быть так.

BTW, во многих ISA регистр возвращаемого значения не является первым регистром, проходящим через arg. И если вы не используете режимы адресации с базой + индексации, это потребует дополнительной инструкции (и привязки другой рег) для strcpy, чтобы скопировать регистр для цикла инкремента указателя.

В инструментальных средствах PDP-11 обычно используется какое-то соглашение о вызове stack-args , всегда нажимающее args на стек. Я не уверен, сколько регистров, поддерживаемых звонками, или списков вызовов, были нормальными, но были доступны только 5 или 6 GP-регистров ( R7 – это счетчик программ, R6 – указатель стека, R5 часто используется как указатель кадра ). Таким образом, он похож на, но даже более стесненный, чем 32-разрядный x86.

 char *bar(char *dst, const char *str1, const char *str2) { //return strcat(strcat(strcpy(dst, str1), "separator"), str2); // more readable to modern eyes: dst = strcpy(dst, str1); dst = strcat(dst, "separator"); // dst = strcat(dst, str2); return dst; // simulates further use of dst } # x86 32-bit gcc output, optimized for size (not speed) # gcc8.1 -Os -fverbose-asm -m32 # input args are on the stack, above the return address push ebp # mov ebp, esp #, Create a stack frame. sub esp, 16 #, This looks like a missed optimization, wasted insn push DWORD PTR [ebp+12] # str1 push DWORD PTR [ebp+8] # dst call strcpy # add esp, 16 #, mov DWORD PTR [ebp+12], OFFSET FLAT:.LC0 # store new args over our incoming args mov DWORD PTR [ebp+8], eax # EAX = dst. leave jmp strcat # optimized tailcall of the last strcat 

Это значительно более компактно, чем версия, которая не использует dst = , и вместо этого повторно использует входной аргумент для strcat . (См. Оба в проводнике компилятора Godbolt .)

Выход -O3 очень отличается: gcc для версии, которая не использует возвращаемое значение, использует stpcpy (возвращает указатель на хвост), а затем mov -immediate, чтобы хранить литеральные строковые данные прямо в нужном месте.

Но, к сожалению, версия dst = strcpy(dst, src) -O3 по-прежнему использует обычный strcpy , а затем строит strcat как strlen + mov -immediate.


Для C-строки или не для C-строки

Строки неявной длины C не всегда являются неотъемлемо плохими и имеют интересные преимущества (например, суффикс также является допустимой строкой, без необходимости ее копировать).

Но строковая библиотека C не разработана таким образом, чтобы обеспечить эффективный код, потому что циклы char -at-a-time обычно не подвергаются автоматическому векторизации, а функции библиотеки отбрасывают результаты работы, которые они должны выполнять.

gcc и clang никогда не автоинъектируют циклы, если счетчик итераций не известен до первой итерации, например for(int i=0; i . ICC может векторизовать петли поиска, но это все еще маловероятно, а также рукописный asm.


strncpy и т. д. - это в основном катастрофа . например, strncpy не копирует завершающий '\0' если он достигает предела размера буфера. По-видимому, он предназначен для записи в середине больших строк, а не для предотвращения переполнения буфера. Не возвращая указатель на конец означает, что вы должны arr[n] = 0; до или после, потенциально касаясь страницы памяти, которую никогда не нужно было трогать.

Несколько функций, таких как snprintf можно использовать и всегда выполнять nul-terminate. Помните, что это сложно, и огромный риск, если вы помните неправильно, поэтому вам нужно каждый раз проверять в тех случаях, когда это важно для правильности.

Как говорит Брюс Доусон: перестаньте использовать strncpy! , По-видимому, некоторые расширения MSVC, такие как _snprintf , еще хуже.

Та же концепция, что и Fluent Interfaces . Просто сделать код быстрее / проще для чтения.

Я не думаю, что это действительно настроено таким образом для целей вложенности, но больше для проверки ошибок. Если память не обслуживает ни одну из стандартных функций библиотеки, они выполняют большую проверку ошибок самостоятельно, и поэтому имеет смысл, что это будет определять, не произошло ли что-то во время вызова strcpy.

 if(strcpy(dest, source) == NULL) { // Something went horribly wrong, now we deal with it } 
  • Почему размер массива не такой же, как и у основного?
  • Функция возвращает None без оператора return
  • Процедуры связанных типов передачи в качестве аргументов
  • Как использовать функцию rand для создания чисел в определенном диапазоне?
  • Как округлить до ближайшего 0,5?
  • Что более эффективно: вернуть значение против Pass by reference?
  • Что такое «статическая» функция?
  • Почему C-массив имеет неправильное значение sizeof (), когда он передается функции?
  • Как не членские функции улучшают инкапсуляцию
  • Разрешение перегрузки C ++
  • Разница между и * звездочкой
  • Interesting Posts

    Ошибка WPF 40 Ошибка BindingExpression path: свойство не найдено в ‘объекте’

    Не удалось выполнить dex: превышение верхнего предела GC в Eclipse

    Хорошая или плохая практика для Dialogs в wpf с MVVM?

    Как установить фоновые рисунки программно в Android

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

    Лучший способ протестировать приложение MS Access?

    Получение «sed error – незаконная последовательность байтов» (в bash)

    Mysql добавляет пользователя для удаленного доступа

    Какие свойства / функции следует искать в хорошем SSD-накопителе?

    В чем разница между записями в fstab?

    Как отображать экран входа только один раз?

    Android HTML ImageGetter как AsyncTask

    Почему C ++ требует, чтобы пользовательский конструктор по умолчанию устанавливал по умолчанию объект const?

    Разница между примечанием @Controller от источника и @RestController

    Название месяца как строка

    Давайте будем гением компьютера.