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


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 A(з A), даючи&00
  4. Loа Ds значення в 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.