Скільки аргументів було передано?


33

Використовуючи обрану мову, напишіть функцію, яка приймає змінну кількість аргументів і повертає кількість аргументів, з якими вона була викликана.

Особливості:

  • Ваша мова повинна підтримувати різні функції аргументів: щось, що можна викликати, що приймає довільну кількість аргументів і повертає значення.
  • Параметри повинні бути спроможні проходити окремо. Це означає, що передача масиву зараховуватиме лише один параметр. Ви можете використовувати масив "всі передані аргументи", якщо ваша мова підтримує його; обмеження на те, як функція викликається.
  • Код, який викликає цю функцію, не повинен вимагати передачі кількості аргументів у своєму джерелі . Якщо компілятор вставляє кількість аргументів як частину конвенції виклику, це дозволено.
  • Аргументи можуть бути будь-якого типу. Ви можете підтримувати лише один тип (наприклад, лише підтримка intвсе ще діє), довільні типи (дозволений будь-який тип аргументів) або будь-яку комбінацію типів аргументів (наприклад, перший аргумент є int, решта - це рядки).
  • Ваша функція може мати максимальну кількість аргументів (тим більше, що ресурси обмежені), але вона повинна підтримувати щонайменше 2 аргументи.

Зразки:

  • f() повертає 0
  • f(1)або f("a")повертається1
  • f([1, 2, 3])повертає 1як переданий масив, а не 3 аргументи
  • f(1, 10)або f(1, "a")повертається2

Оскільки це код-гольф, виграшним рішенням є те, що використовує найменшу кількість байтів.


4
Не зовсім зрозуміло (об'єктивно), що таке "функція", "повертається значення" чи "варіативні аргументи". Наприклад, чи вважали б функцію Додос монадічною чи варіатичною?
користувач202729

24
@ user202729 Якщо ваша мова не підтримує функції, використовуйте іншу мову. Це не вимога, що всі мови можуть змагатися, частина коду з гольфу - це пошук правильного інструменту для роботи.
Санчіз

5
@ user202729 У мене немає проблем з випадковим завданням, спрямованим на традиційні мови / мови високого рівня, так само, як у нас виникають випадкові завдання, які можливі лише для незвичних мов.
Санчіз

6
не знав, що нам доведеться вирішити проблему зупинки, щоб мовні характеристики мали чіткий виклик ....
Conor O'Brien

5
Якщо у вашій мові немає поняття аргументів / умовного виклику, вона не відповідає критеріям підтримки довільної кількості аргументів.
Гленн Сміт

Відповіді:


15

Двійковий дзвінок Amstrad CPC Z80 від BASIC, 1 байт, шістнадцятковий

C9          : RET

(Також 2 та 5 байт версії див. Нижче)

Після вступу на виклик кількість пройдених параметрів буде в Aреєстрі. Код просто повертається негайно. У Z80 не існує концепції повернення значень, лише стан входу та виходу. Значення просто "там" доступне в реєстрі, оскільки код не змінює жодних умов введення, крім PC(лічильник програми) та SP(покажчик стека). Однак значення AIN недоступне для BASIC і перезаписується практично відразу.

Приклади:

CALL &8000, "Hello", "World"

A = 2

CALL &8000, 42

A = 1

CALL &8000

A = 0


За запитом, ось якийсь код, який робить значення доступним у BASIC. Я був дуже здивований, виявивши, що це можна зробити всього в 5 байтах!

Код машини:

12          : LD   (DE), A
13          : INC  DE
AF          : XOR  A
12          : LD   (DE), A
C9          : RET

При вступі:

  • AF - регістри акумулятора та прапорів (трактуються як два 8-бітні регістри)
    • A містить кількість пройдених параметрів, максимум до 32 параметрів
    • Я не впевнений, що там F. Схоже, всі прапори RESET to 0, за винятком двох невизначених прапорів, які є обома 1. ZПрапор (нуль) встановлено в положення, 1якщо не було проходили в параметри
  • BC
    • B- 32 мінус кількість параметрів ( A+ B= 32)
    • C - &FF
  • DE - Адреса останнього параметра або адреса виклику, якщо не були передані параметри
  • HL - Адреса першого байту після введеної токенізованої команди BASIC (або як програма, або в режимі безпосередньої команди)
  • IX - адреса стека вказівника на останній параметр
  • IY - &0000

Кодекс

  1. Loa Ds адресу, на яку вказує DEзначення уA
  2. INCременти DE
  3. XORs AA), даючи&00
  4. LDs значення в Aадресу, на яку вказуєDE
  5. RETурни

На виході:

  • Aзнищується (це завжди &00)
  • DE знищується (це завжди на один вищий, ніж під час вступу)
  • Усі інші регістри збережені

ОСНОВА

Amstrad basic має лише три типи даних, а також прості масиви. За замовчуванням всі BASIC змінні є РЕАЛЬНІ (підписані, 32-бітна мантіса, 8-бітний експонент), що можна зробити явними за допомогою !. Для використання INTEGER (16-бітний підпис) %та для STRING (1 байт довжиною рядка, до 255 байт даних символів, двійковий безпечний) $:

  • x - РЕАЛЬНА (неявна)
  • x! - РЕАЛЬНА (явна)
  • x% - ІНТЕГЕР
  • x$ - STRING

Ви також можете використовувати DEFINT, DEFREALі DEFSTRз однією літерою або діапазоном двох окремих літер , щоб вказати тип за замовчуванням для всіх змінних , що починаються з цієї букви, схожою на FORTRAN.

  • DEFSTR a
  • DEFINT x-z

Зараз:

  • a - STRING (неявно)
  • i - РЕАЛЬНА (неявна)
  • x - INTEGER (неявно)
  • x$ - STRING (явно)

Найпростіший тип роботи з цілим числом. Машинний код очікує, що останній параметр буде переданий за адресою, а не значенням, через що @префікс до змінної. Змінна повернення вважається одним із CALLs параметрів.

Машинний код називається так, як випливає з BASIC (якщо припустити, що він завантажений в пам'ять за адресою &8000):

CALL &8000, "Hello", "World", 42, @n%

n% = 4

Це завжди дасть правильний результат, незалежно від початкового значення n%.

Для двобайтової версії, яка зберігає всі вхідні регістри:

CALL &8003, "Hello", "World", 42, @n%

n% = 4

Це пропускає перші три байта, а тільки дає правильний результат , якщо початкове значення n%є 0- 255. Це працює, тому що Z80 є малоінтенсивним.

Параметр повернення повинен бути ініціалізований перед передачею, інакше BASIC видасть Improper argumentпомилку. На зображенні нижче я друкую (за допомогою ярлика, ?оскільки я також переграв демонстрацію!) Повертаються значення безпосередньо перед і після виклику, щоб показати значення, що змінюється. Я використовую значення, &FFFFоскільки це двійкове представлення -1для підписаного цілого числа. Це демонструє, що 5-байтова програма правильно записує обидва байти, тоді як 2-байтна програма записує лише низький байт і припускає, що високий байт вже є &00.

enter image description here


Тож як діє умова виклику, за якою ви використовуєте зворотні значення? Якщо вони не повертають їх у акумуляторі, або взагалі, то ваша відповідь - це, головним чином, винайдення користувацької конвенції про виклики, яка вирішує проблему для вас (додавши реєстр повернень, а не передаючи вказівник, де ви можете зберігати A, якщо це як ти насправді може це зробити з BASIC). Не те, що в цьому щось не так, але це може бути цікавішою відповіддю дотримуватися існуючої конвенції про виклик.
Пітер Кордес

@PeterCordes Ні Amstrad BASIC, ні Z80 не мають концепції застосування. Усі значення є глобальними і одразу доступні до їх знищення. Значення Aє однаковим відразу після RETінструкції. Час життя значення у Aдуже короткий, оскільки це акумулятор. Немає такого x = CALL &8000, 42. Треба було б бути CALL &8000, x, 42і додатковим кодом Z80, але тоді xбуло б 2, ні 1.
CJ Dennis

Я думаю, що це добре, якщо ви включите аргумент виводу у підрахунок, інакше є 1-байтна інструкція про декремент, чи не так? Мені було б цікаво побачити версію, яку насправді застосував BASIC, а не банальну.
Пітер Кордес

1
@PeterCordes Готово! О, до речі, я забув згадати, щоб не називати його без параметрів, оскільки він замінить власні перші дві інструкції з &00s - NOPno-ops. Ще один байт можна додати, щоб зробити його більш безпечним, але, звичайно, без параметра повернення він нічого не може встановити.
CJ Dennis

32

29
Ява побиває Явскрипт рідко, що можна помітити
Випадковий хлопець

3
@Therandomguy для цього потрібно interface x{void f(Object...a);}визначити щось на кшталт , і ця лямбда повинна бути або збережена у змінній цього типу інтерфейсу, або передана методу, що очікує цього типу інтерфейсу, тому я не дуже впевнений, що це враховує цю проблему (навіть Хоча зазвичай лямбди з Java допускаються у викликах кодогольфа)
SamYonnou

3
@SamYonnou Немає різниці з іншими лямбдами, і як ви вже згадували, лямбдаси - це добре .
Олів'є Грегоар

@ OlivierGrégoire Я знаю, що лямбдаси дозволені, мою думку було те, що порівняно з JavaScript, наприклад, вам потрібно набагато більше додаткового коду, щоб налаштувати його, навіть якщо ви використовуєте щось на зразок REPL і уникаєте необхідності в основному класі / методі ( необхідність визначення інтерфейсу - це те, що відрізняє його від JavaScript)
SamYonnou

@ OlivierGrégoire: Я знаю деякі Java, але взагалі не в курсі. Мені було цікаво побачити коментар Сема щодо того, яка котлованна плита під килимком відповідає у відповідь Java, яка дозволяє насправді бути дуже короткою. Я погоджуюся, що це слід дозволити (навіть якщо він дає вам щось, що зазвичай не отримує з функціями Java, правда, тож це не просто скорочення котла, це дає вам вбудований аргумент). Крім того, це все ще цікаво як відповідь на "Java побиття JS".
Пітер Кордес

25

JavaScript, 15 байт

[].push.bind(0)

Array.prototype.pushФункція приймає будь-яку кількість аргументів, додає їх в масив і повертає розмір масиву. Тому pushфункція, що використовується в порожньому масиві, повертає кількість аргументів, що надсилаються push.

f = [].push.bind(0)

f(10,2,65,7)
> 4

f()
> 0

.bind(0)Просто дає pushфункцію фіксоване thisзначення , так що вона може бути збережена в змінної. Насправді 7-байтний ідентифікатор [].pushможна використовувати буквально (але не призначати) без bind:

[].push(10,2,65,7)
> 4

[].push()
> 0


18

Haskell , 108 107 95 94 байт

class T r where z::Int->r
instance T Int where z=id
instance T r=>T(a->r)where z n _=z$n+1
z 0

Спробуйте в Інтернеті!

Напрочуд важко було працювати, але мені було весело, намагаючись дізнатися, як реалізувати щось, що є тривіальним, в імперативних мовах.


Чорт, ти побив мене до цього. fнеобов'язково, якщо ви скажете, що z 0це функція без прив'язки, тому main = print $ ((z 0) pi 0 () [] :: Int)працює.
Ангс

І я маю на увазі, що типи працюють, коли вони використовуються як анонімна функція, тому ви можете видалити все з двох останніх рядків, за виняткомz 0
Angs

Приємно, дякую! Виявляється, я робив щось не так, коли тестував анонімну функцію. Спробував ваш приклад, і це спрацювало чудово.
користувач9549915

Я думаю, що ::Intслід вважати кількість байтів, оскільки тип відповіді повинен бути оголошений рано чи пізно, як у main = print $ ((z 0 :: Double -> Integer -> () -> [a] -> (Int->Int->Int) -> IO () -> Int) pi 0 () [] (+) main). Я також думаю, що це працює лише під час компіляції, тому щось подібне foldl(\a b->a b) (z 0) $ [1..5])::Intне може працювати. Так чи інакше, це чудові речі.
Ангс

2
s/imperative/non-curry/
користувач202729


12

Хоча його, мабуть, слід завернути:f(){ echo $#; }
muru

8
@muru Це добре виглядає як повноцінна програма для мене.
Ніл

Хоча, я зараз бачу, що ОП хоче лише функцію ...
Ніл

2
Сценарії оболонки @Neil діють точно так само, як і функції. ОП не зрозуміло, що таке функція, я вважаю, що моє подання - це лише функція, збережена на диску.
Павло

9

Мозок-Флак , 6 байт

Моє перше рішення Brain-Flak, гідне публікації, я думаю, що це правильний інструмент для цієї роботи:

([]<>)

Спробуйте в Інтернеті!

Пояснення

Під час виконання програми Brain-Flak спочатку лівий стек містить усі аргументи. Звідти це просто питання:

(      -- push the following..
 []    --   height of the stack (ie. # of arguments)
   <>  -- ..to the other stack  (toggles to the other stack)
)      --
       -- the right stack now contains the # of arguments which
       -- gets printed implicitly

7

Мова Вольфрама (Mathematica) , 11 байт

Tr[1^{##}]&

Спробуйте в Інтернеті!

Запропоновано JungHwan Min. Деякі обмеження (вхід має бути прямокутним), але ми не зобов'язані обробляти довільні введення.

11 байт

Length@!##&

Спробуйте в Інтернеті!

Ще одне байтне рішення, запропоноване Мартіном Ендером. Здається, це помилка, коли немає одного вводу, але він все одно повертає правильне значення у всіх випадках.

12 байт

Length@{##}&

Спробуйте в Інтернеті!

Моє оригінальне рішення.

У Mathematica ##розшифровується кількість аргументів функції. {і }загортає їх у список та Length@приймає довжину цього списку. &в кінці перетворює це на фактичну функцію.


7

R , 30 байт

function(...)length(list(...))

Спробуйте в Інтернеті!


1
function(...)nargs()становить 20 байт, але використання length(...)було моїм початковим підходом до тих пір, поки я не nargsпогукнув функцію -подобний
Джузеппе

@Giuseppe Хм, я спробував перетворити на list(...)логічне, щоб sum()можна було використовувати, але це хитро: /
JAD

1
Ха-ха, не намагайся
привчати

1
@RoryT о насправді, R документи говорять комбінувати. Nevermind: D
JAD

2
...length() робить те ж саме , якlength(list(...))
Giuseppe

7

Bash, 12 байт (завдяки paxdiablo за збереження 4)

n()(echo $#)

Скопіюйте та вставте на підказці. Потім запустіть n функцію з підказки:

$ n
0
$ n 46 gr 3443 dad
4
$ n 4fwj23 wrw jdwj 00998 34 eyt q3 vg wq j qw
11

2
Ласкаво просимо до PPCG!
Мартін Ендер

ви можете просто сказати, що це сценарій "./n", а не функція? то це просто:, echo $#7 байт. (Буде тоді будь-яка оболонка використовується для запуску «./n» сценарій з тобто запустити Баш тоді , коли вам:.? ./n arg1 ... argnЦе буде витлумачено баш.)
Олів'є Дюлак

@Olivier Dulac Проблема чітко говорить про функцію.
Wastrel

7

C ++ 14 (gcc) , 34 байт

Як загальна варіативна функція лямбда (потрібна C ++ 14):

[](auto...p){return sizeof...(p);}

Спробуйте в Інтернеті!

Попередня (неправильна) відповідь: 32 байти

У ньому бракувало template<class...T>і(p)

int f(T...p){return sizeof...p;}

6
C ++ 14, C ++ 11 не має родових лямбдів.
Квентін


@nwp: не -fpermissiveкоштував би вам 12 байт для цього варіанту? Якщо це не стандарт ISO C ++ або GNU C ++.
Пітер Кордес

@PeterCordes Це, мабуть, робить і має на меті уникнути наявності тривіального 0-байтового рішення для всього, передаючи програму через командний рядок. Я просто не думав про це тут, тому що це, здається, не образливо.
nwp

@Quentin fix -> C ++ 14
Bierpfurz


5

Октава , 9 байт

@()nargin

Спробуйте в Інтернеті!

Анонімна функція приймає будь-яку кількість аргументів (і мовчки відкидає лот) і виводить кількість аргументів через вбудований nargin. Це не працює в MATLAB, де вам потрібно vararginбуде дозволити безліч довільних аргументів.



4

Швидкий пошук відповідей на всій сторінці, схоже, свідчить про те, що ви можете залишити лишеsub
ASCII лише

2
Підказка: TIO давайте скопіюйте у формат публікації PPCG (ESC, S, G)
лише для ASCII

@ ASCII-тільки О, приємно, дякую! :) Що стосується того, щоб вийти з цього місця sub, я не думаю. Без нього це не функція.
Кріс

@ ASCII Лише я вважаю відповіді subнедійсними, оскільки результат - це не те, що можна зателефонувати чи призначити змінній
Тон Хоспел

4

PHP, 34 байти

function(...$a){return count($a);}

Хороший! Альтернативою для PHP 5.6 і старшої версії є function(){return func_num_args();}(35 байт, розміщена нижче).
Ісмаїл Мігель

@IsmaelMiguel Цей також працює в PHP 5.6 .
аксіак

1
Ви повинні згадати це про роботу лише в PHP 5.6 та новіших версіях .
аксіак

@axiac Ти маєш рацію, моя погана. Думав про PHP5.5
Ісмаель Мігель

4

C # .NET, 11 байт

a=>a.Length

Спробуйте в Інтернеті.

Пояснення:

У C # .NET objectвикористовується для аргументів багатьох типів, що дозволяє передавати цілі числа, рядки, символи тощо як можливі входи. Наприклад:

// Can be called like: `F(2)`, `F("test")`, `F('a')`, etc.
void F(object arg){ ... }

C # .NET також може мати фіксований розмір необов'язкових аргументів. Наприклад:

// Can be called like: `F()`, `F(2)`, `F("test")`, `F('a')`, etc.
void F(object arg = null){ ... }

І є також вараги, що є невизначеною кількістю необов'язкових аргументів (саме це я використав у цій відповіді). Наприклад:

// Can be called like: `F()`, `F(2)`, `F(2, "test", 'a')`, etc.
void F(params object[] args){ ... }

Зазвичай лямбди створюються так:

System.Func<object[], int> F f = a=>a.Length;
// A call like `f(new object[]{2, "test", 'a'))` will return 3 (size of the input array)

Але, на жаль System.Func, не підтримує paramsvarargs, тому мені доведеться створити delegateнатомість:

delegate int F(params object[] args);
F f = a=>a.Length;
// A call like `f()` will return 0, and `f(2, "test", 'a')` will return 3

Що є моєю відповіддю на це завдання, і його можна знайти у пов'язаному тестовому коді TIO.


Єдине обмеження полягає в тому, що введення фактичного object[]вподобання f(new object[]{1,2,3})призведе до 3 замість 1. f(new int[]{1,2,3})все одно призведе до 1, оскільки він інтерпретує значення int[]як єдине object. Для того, щоб мати object[]параметр можна інтерпретувати як єдиний об'єкт , а також він може бути перетворений в об'єкт , як це: f((object)new object[]{1,2,3}).


Я мушу сказати, якби коли-небудь була відповідь, яка змусила мене підтримати, включаючи лямбда-бойлер у відповідях C #, це був би цей ... але це, безумовно, правильне рішення.
Каміль Дракарі

@KamilDrakari Можливо, справді було не зовсім зрозуміло, що я зробив, не відкриваючи TIO-посилання, тому я додав пояснення.
Кевін Кройсейсен

1
@Taemyr Я намагався знайти рішення, але , до жаль , не існує ні для C # .NET для лиття будь-яких , крім object[]параметрів object, наприклад: f((object)new object[]{1,2,3});. Немає можливості розмежовувати f(new object[]{1,2,3});і f(1,2,3);наскільки я міг знайти.
Кевін Крейссен

1
це правильно обробляє параметри масиву для величезного штрафу байтів. Можливо, є більш лаконічна структура, яка може впоратися з цим, але вона працює в моєму тестуванні.
Каміль Дракарі

1
@KamilDrakari Хм, але він не вдається f(1, new object[]{1,2,3})знову. Не впевнений, чи можна знайти рішення для такої поведінки.
Кевін Крейссен

4

Додо , 32 31 байт

f
	dot i f dab
i
	
	dip dot dab

Спробуйте в Інтернеті!

Використовує функцію приросту Денніса.

Пояснення

f                     # definition of f - target function
        dot i f dab   # sum of j(f(all args but first)). recurses until it has 0 args
i                     # definition of i - returns (arg, 1) given 1 arg
                      # arg
        dip dot dab   # 1 (dot dab on list of length 1 returns 0, dip returns |0 - 1|)

Крім того, 32 байти без рекурсії в цільовій функції (спасибі @Leo )

	dot i
i
	dip dot dab dot
	i dab

Спробуйте в Інтернеті!

Пояснення

        dot i             # anonymous function: sum of i(args)
                          # here this becomes implicit main
i                         # definition of i - returns a list with all arguments replaced with 1
        dip dot dab dot   # 1 (dab dot returns empty list, dot returns 0, dip returns |0 - 1|
        i dab             # list concatenated with i(all args but first)

Ось ще одне рішення такої ж довжини. Спробуйте в Інтернеті! Я, здається, не розумію, чому твої роботи працюють, чи не могли б ви додати пояснення?
Лев

Гей, ви додали пояснення до мого рішення! Я хотів одного для вашого, я знаю, як працює моя xD
Лев

1
@Leo вибачте за несвоєчасну відповідь, ідею, що я роблю, тільки що скопіював функцію Денніса, спробую зрозуміти якнайшвидше. Я не мав уявлення про те, як працює додо, тому я зрозумів, що твій зробив першим
лише ASCII

Не хвилюйтесь, це була просто кумедна ситуація :)
Лев

@Leo нормально, чи має моє пояснення сенс? (зверніть увагу: я перебуваю на мобільному пристрої, тому сміливо редагуйте його, щоб зробити його кращим, хам)
лише ASCII

3

C ++, 72 байти

int f(){return 0;}template<class...P>int f(int,P...p){return f(p...)+1;}

Зберігає байти, працюючи лише з ints.


Можна використовувати sizeof....
LF

3

Іржа, 57 байт

macro_rules!f{()=>{0};($($x:expr),+)=>{[$($x),+].len()};}

Пояснення:

macro_rules! f {         // define a macro called f
    () => {0};           // when called without arguments, expand to 0
    ($($x:expr),+) => {  // when called with 1 or more comma seperated arguments
        [                // rust uses [a, b, c] to make an array
            $($x),+      // expand to the arguments seperated with a comma
        ]                
        .len()           // take the length of that.
    };
}

Тест:

fn main() {
    println!("{:?}", f!());                // prints 0
    println!("{:?}", f!(4));               // prints 1
    println!("{:?}", f!(5, 2));            // prints 2
    // works with anything, as long as you dont mix things
    println!("{}", f!("", "a", "hello"));  // prints 3
}




2

PHP, 11 байт

<?=$argc-1;

Спробуйте в Інтернеті: 1 вхід | 3 входи


Я не дуже впевнений у цьому (і це дійсність), оскільки це кількість аргументів, переданих для виклику PHP.
Ісмаїл Мігель

@IsmaelMiguel, дивіться цей консенсус .
Кудлатий

1
Питання явно вимагає функції, яка повертає число, не відображає його: "... написати функцію, яка приймає змінну кількість аргументів і повертає кількість аргументів."
аксіак

1
Повторне цитування запитання: "Використовуючи обрану мову, напишіть функцію, яка приймає змінну кількість аргументів і повертає кількість аргументів, з якими вона була викликана." Ваш код не містить функцій.
Ісмаїл Мігель

@IsmaelMiguel, якби це було дійсно так, то багато інших рішень також були б недійсними. Норма полягає в тому, щоб рішення дозволяли бути програмами або функціями.
Кудлатий

2

Пакетна, 50 49 байт

set n=0
for %%a in (%*)do set/an+=1
exit/b%n%

В Партії немає вбудованих, тому нам доводиться ходити по старому школі. Збережено 1 байт завдяки @IsmaelMiguel. Виводиться через код виходу, або збережіть 3 байти, якщо вихід через глобальну змінну є дійсним. Приклад використання в повній програмі:

@echo off
call:c %*
echo %ERRORLEVEL%
exit/b
:c
set n=0
for %%a in (%*)do set/an+=1
exit/b%n%

Я вважаю, що ця відповідь - це відповідь (дещо) суперечить правилам. Пакет має щось дещо близьке до функцій. Ви можете зробити щось подібне до :a|set r=0&for %%a in (%*)do set/ar+=1( |= newline line-style). Цей розчин становить 38 байт. Для його виконання виконайте функцію call :a <args>a goto :eofперед функцією, яка є значенням, доступним всередині змінної r. Якщо ви хочете зберегти своє рішення, видаліть /aперший setі видаліть його @.
Ісмаїл Мігель

@IsmaelMiguel Як це? (Примітка. Я не включив ім’я функції в кількість байтів, але я включив функцію повернення, що здається розумним, оскільки там має бути десь.)
Ніл

Так, саме так. Гарний вилов з вихідним кодом! Я був здивований, побачивши, що вихідні коди можуть бути більшими, ніж 255. Прикладом є список, наданий Symantec: symantec.com/connect/articles/…
Ісмаель Мігель

2

32-бітна (i386) функція машинного коду x86, 13 байт

Конвенція виклику: Система i386 V (стеки арг), з покажчиком NULL як дозорний / термінатор для кінця аргументу списку . (Clobbers EDI, інакше відповідає SysV).

C (і ASM) не передають інформацію про тип різноманітним функціям, тому опис ОП передачі цілих чисел або масивів без явної інформації про тип може бути реалізований лише в конвенції, яка передає якийсь об'єкт структури / класу (або покажчики на такий ), а не оголені цілі числа на стеку. Тому я вирішив припустити, що всі аргументи були не-NULL-покажчиками, а абонент передає NULL-термінатор.

Список покажчиків аргументів, що закінчується NULL, фактично використовується в C для таких функцій, як POSIXexecl(3) : int execl(const char *path, const char *arg, ... /* (char *) NULL */);

C не дозволяє int foo(...);прототипам без фіксованого аргументу, але int foo();означає те саме: аргументи не вказані. (На відміну від C ++, де це означає int foo(void)). У будь-якому випадку, це відповідь asm. Спільний компілятор С для виклику цієї функції безпосередньо цікавий, але не обов'язковий.

nasm -felf32 -l/dev/stdout arg-count.asm видалено рядки з коментарями.

24                       global argcount_pointer_loop
25                       argcount_pointer_loop:
26                               .entry:
28 00000000 31C0             xor   eax, eax  ; search pattern = NULL
29 00000002 99               cdq             ; counter = 0
30 00000003 89E7             mov   edi, esp
31                       ;    scasd           ; edi+=4; skip retaddr
32                       .scan_args:
33 00000005 42               inc   edx
34 00000006 AF               scasd            ; cmp eax,[edi] / edi+=4
35 00000007 75FC             jne  .scan_args
36                       ;    dec   edx       ; correct for overshoot: don't count terminator
37                       ;    xchg  eax,edx
38 00000009 8D42FE           lea   eax, [edx-2]    ; terminator + ret addr
40 0000000C C3               ret

size = 0D               db $ - .entry

Питання показує, що функція повинна вміти повертати 0, і я вирішив виконати цю вимогу, не включаючи закінчуючий вказівник NULL у число аргументів. Однак це коштує 1 байт. (Для 12-байтової версії вийміть LEA і розметайте scasdзовнішній цикл та на xchg, але не на dec edx. Я використовував LEA, оскільки він коштує так само, як і ці три три інструкції, укомплектовані, але він більш ефективний, тому функцій менше упс.)

C абонент для тестування :

Побудовано з:

nasm -felf32 -l /dev/stdout arg-count.asm | cut -b -28,$((28+12))- &&
 gcc -Wall -O3 -g -std=gnu11 -m32 -fcall-used-edi arg-count.c arg-count.o -o ac &&
 ./ac

-fcall-used-ediпотрібно навіть при -O0 сказати gcc припускати, що функція clobber ediне зберігає / не відновлює її, оскільки я використав стільки викликів в одному операторі C ( printfвиклик), що навіть -O0використовував EDI. Це, мабуть, є безпечним для того, mainщоб gcc's клобувати EDI від власного абонента (у CRT-коді), в Linux з glibc, але в іншому випадку змішувати / відповідати коду, компільованому з різними, абсолютно неправдиво -fcall-used-reg. Немає його __attribute__версії, яка б дозволила нам оголосити функції asm зі спеціальними умовами викликів, відмінними від звичайних.

#include <stdio.h>

int argcount_rep_scas();       // not (...): ISO C requires at least one fixed arg
int argcount_pointer_loop();   // if you declare args at all
int argcount_loopne();

#define TEST(...) printf("count=%d = %d = %d   (scasd/jne) | (rep scas) | (scas/loopne)\n", \
        argcount_pointer_loop(__VA_ARGS__), argcount_rep_scas(__VA_ARGS__), \
        argcount_loopne(__VA_ARGS__))

int main(void) {
    TEST("abc", 0);
    TEST(1, 1, 1, 1, 1, 1, 1, 0);
    TEST(0);
}

Дві інші версії також увійшли в 13 байт: ця заснована на loopneповерненні значення, яке занадто високе на 1.

45                       global argcount_loopne
46                       argcount_loopne:
47                           .entry:
49 00000010 31C0             xor   eax, eax  ; search pattern = NULL
50 00000012 31C9             xor   ecx, ecx  ; counter = 0
51 00000014 89E7             mov   edi, esp
52 00000016 AF               scasd           ; edi+=4; skip retaddr
53                       .scan_args:
54 00000017 AF               scasd
55 00000018 E0FD             loopne  .scan_args
56 0000001A 29C8             sub   eax, ecx
58 0000001C C3               ret

size = 0D = 13 bytes               db $ - .entry

Ця версія використовує rep scasd замість циклу, але приймає аргумент підрахунку аргументу 256. (Або обмежено на 256, якщо верхні байти ecxдорівнюють 0!)

63                       ; return int8_t maybe?
64                       global argcount_rep_scas
65                       argcount_rep_scas:
66                               .entry:
67 00000020 31C0             xor   eax, eax
68                           ;    lea   ecx, [eax-1]
69 00000022 B1FF             mov   cl, -1
70 00000024 89E7             mov   edi, esp
71                       ;    scasd              ; skip retaddr
72 00000026 F2AF             repne scasd        ; ecx = -len - 2 (including retaddr)
73 00000028 B0FD             mov   al, -3
74 0000002A 28C8             sub   al, cl       ; eax = -3 +len + 2
75                       ;    dec   eax
76                       ;    dec   eax
77 0000002C C3               ret

size =  0D = 13 bytes         db $ - .entry

Цікаво, ще одна версія , заснована на inc eax/ pop edx/ test edx,edx/ jnzувійшов в 13 байт. Це умовний виклик callee-pops, який ніколи не використовується реалізаціями C для варіативних функцій. (Я перескакував ret addr в ecx, а jmp ecx замість ret. (Або натисніть / ret, щоб не порушити стек передбачувача зворотної адреси).




Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.