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


33

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

Особенности:

  • Ваш язык должен поддерживать функции аргументов с переменным аргументом: что-то вызываемое, которое принимает произвольное количество аргументов и возвращает значение.
  • Параметры должны передаваться индивидуально. Это означает, что передача массива будет учитываться только для одного параметра. Вы можете использовать массив «все переданные аргументы», если ваш язык поддерживает его; ограничение заключается в том, как вызывается функция.
  • Код, вызывающий эту функцию, не обязан передавать количество аргументов в ее источнике . Если компилятор вставляет количество аргументов как часть соглашения о вызовах, это разрешается.
  • Аргументы могут быть любого типа, который вы хотите. Вы можете поддерживать только один тип (например, поддерживается только поддержка int), произвольные типы (разрешен любой тип аргумента) или любую комбинацию типов аргумента (например, первый аргумент is int, остальные - строки).
  • Ваша функция может иметь максимальное количество аргументов (особенно если ресурсы конечны), но должна поддерживать как минимум 2 аргумента.

Образцы:

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

Поскольку это код-гольф, выигрышное решение - это то, которое использует наименьшее количество байтов.


4
Не совсем ясно (объективно), что такое «функция», «возвращаемое значение» или «переменные аргументы». Например, будет ли функция Додоса считаться монадической или вариадной?
user202729

24
@ user202729 Если ваш язык не поддерживает функции, используйте другой язык. Это не требование, что все языки могут конкурировать, часть игры в гольф кода находит правильный инструмент для работы.
Sanchises

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

6
не знал, что мы должны были решить проблему остановки для языковых характеристик, чтобы иметь четкую задачу ....
Конор О'Брайен,

5
Если ваш язык не имеет концепции аргументов / соглашения о вызовах, тогда он не соответствует критериям поддержки произвольного числа аргументов.
Гленн Смит

Ответы:


15

Бинарный вызов Amstrad CPC Z80 от BASIC, 1 байт, шестнадцатеричный код

C9          : RET

(Также 2- и 5-байтовые версии, см. Ниже)

При поступлении на звонок количество передаваемых параметров будет в Aреестре. Код просто возвращается немедленно. В Z80 нет понятия возвращаемых значений, только состояния входа и выхода. Значение просто «там» доступно в регистре, поскольку код не изменяет входные условия, кроме PC(счетчик программы) и SP(указатель стека). Однако значение в Aнедоступно для Бейсика и почти сразу же перезаписывается.

Примеры:

CALL &8000, "Hello", "World"

A = 2

CALL &8000, 42

A = 1

CALL &8000

A = 0


По запросу, здесь есть некоторый код, который делает значение доступным в бейсике. Я был очень удивлен, обнаружив, что это можно сделать всего за 5 байтов!

Машинный код:

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

На входе:

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

Код

  1. Loa Ds адрес, на который указывает DEзначение вA
  2. INCrements DE
  3. XORс AA), давая&00
  4. Loa Ds значение в Aадрес, на который указываетDE
  5. RETурны

На выходе:

  • Aуничтожен (это всегда &00)
  • DE уничтожается (всегда на единицу выше, чем при входе)
  • Все остальные регистры сохраняются

Базовый

Amstrad basic имеет только три типа данных плюс простые массивы. По умолчанию все переменные BASIC имеют значение REAL (со знаком, 32-битная мантисса, 8-битная экспонента), что можно сделать явным с помощью !. Для использования %в формате INTEGER (со знаком, 16 бит) и для STRING (длина строки в 1 байт, до 255 байтов символьных данных, бинарный код) используйте $:

  • x - НАСТОЯЩИЙ (неявный)
  • x! - НАСТОЯЩИЙ (явный)
  • x% - INTEGER
  • x$ - STRING

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

  • DEFSTR a
  • DEFINT x-z

В настоящее время:

  • a - STRING (неявный)
  • i - НАСТОЯЩИЙ (неявный)
  • x - INTEGER (неявный)
  • x$ - STRING (явный)

Самый простой тип для работы с целым числом. Машинный код ожидает, что последний параметр будет передан по адресу, а не по значению, поэтому @к переменной добавляется префикс. Возвращаемая переменная считается одним из CALLпараметров s.

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

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

n% = 4

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

Для 2-байтовой версии, которая сохраняет все входные регистры:

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

n% = 4

Это пропускает первые три байта и дает правильный результат, только если начальное значение n%равно 0- 255. Это работает, потому что Z80 имеет младший порядок.

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

введите описание изображения здесь


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

@PeterCordes Ни у Amstrad BASIC, ни у Z80 нет концепции областей применения. Все значения являются глобальными и сразу доступны, пока не уничтожены. Значение Aто же самое сразу после RETинструкции. Срок службы значения в Aочень короткий, так как это аккумулятор. Там нет такого понятия, как x = CALL &8000, 42. Это должно было быть CALL &8000, x, 42и дополнительный код Z80, но тогда xбыло бы 2, нет 1.
CJ Деннис

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

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

32

29
Java, бьющий Javscript, редко встречается, чтобы его заметили
Случайный парень

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

3
@ SamYonnou Нет никакой разницы с другими лямбдами, и, как вы упомянули, с лямбдами все в порядке .
Оливье Грегуар

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

@ OlivierGrégoire: Я знаю немного Java, но не поспевал за ним вообще. Мне было интересно увидеть комментарий Сэма о том, что шаблонная информация попадает под ковер в ответе Java, что позволяет ему быть действительно коротким. Я согласен, что это должно быть разрешено (даже несмотря на то, что оно дает вам то, чего вы обычно не получаете с помощью функций Java, верно, так что это не просто шаблонное сокращение, это дает вам встроенный подсчет аргументов). Кроме того, он по-прежнему интересен как ответ на «Java beating 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)работает.
Angs

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

Хорошо, спасибо! Оказывается, я делал что-то не так, когда тестировал анонимную функцию. Опробовал твой пример и все заработало просто отлично.
user9549915

Я полагаю, что ::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не может работать. В любом случае, это отличные вещи.
Angs

2
s/imperative/non-curry/
user202729


12

Хотя это, вероятно, должно быть завернуто:f(){ echo $#; }
Muru

8
@muru Это выглядит как полноценная программа для меня.
Нил

Хотя теперь я вижу, что ОП хочет только функцию ...
Нил

2
Скрипты оболочки @Neil действуют точно так же, как функции. ОП не понимает, что это за функция, я утверждаю, что мое представление - это просто функция, сохраненная на диске.
Павел

9

Brain-Flak , 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

Wolfram Language (Mathematica) , 11 байт

Tr[1^{##}]&

Попробуйте онлайн!

Предложенный JungHwan Мин. Некоторые ограничения (ввод должен быть прямоугольным), но мы не обязаны обрабатывать произвольный ввод.

11 байт

Length@!##&

Попробуйте онлайн!

Еще одно 11-байтовое решение, предложенное Мартином Эндером. Это кажется ошибкой, когда нет одного ввода, но оно все равно возвращает правильное значение во всех случаях.

12 байт

Length@{##}&

Попробуйте онлайн!

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

В Mathematica ##обозначает переменное число аргументов в функции. {и }оборачивает их в список и Length@принимает длину этого списка. &в конце превращает это в реальную функцию.


7

R , 30 байт

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

Попробуйте онлайн!


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

@Giuseppe Хм, я попытался преобразовать в list(...)логическое, чтобы sum()можно было использовать, но это сложно: /
JAD

1
Ха-ха, не пытайся
заманить

1
@RoryT О, на самом деле, R документы говорят объединить. Неважно: D
JAD

2
...length() делает то же самое, что иlength(list(...))
Джузеппе

7

Bash, 12 байт (спасибо paxdiablo за сохранение 4)

n()(echo $#)

Скопируйте и вставьте в командной строке bash. Затем запустите функцию 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". То есть вы запускаете bash? тогда когда вы: ./n arg1 ... argnэто будет интерпретироваться bash.)
Оливье Дюлак

@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 исправлено -> C ++ 14
Bierpfurz


5

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

@()nargin

Попробуйте онлайн!

Анонимная функция, принимающая любое количество аргументов (и молча отбрасывающая лот), и выводящая количество аргументов через встроенную функцию nargin. Это не работает в MATLAB, где вам нужно vararginучесть произвольное множество аргументов.



4

Perl 5, 9 bytes

sub{~~@_}

Try it online!


A quick sitewide search of answers seems to indicate that you can leave out the sub
ASCII-only

2
Protip: TIO let's you copy in PPCG post format (ESC, S, G)
ASCII-only

@ASCII-only Oh nice, thanks! :) As for leaving out the sub, I don't think so. It's not a function without it.
Chris

@ASCII-only I'd consider answers without sub invalid since the result isn't something you can call or assign to a variable
Ton Hospel


4

C# .NET, 11 bytes

a=>a.Length

Try it online.

Explanation:

In C# .NET object is used for multi-type arguments, allowing one to pass integers, strings, characters, etc. as possible inputs. For example:

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

C# .NET can also have a fixed size of optional arguments. For example:

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

And there are also varargs, which is an undefined amount of optional arguments (which is what I've used in this answer). For example:

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

Usually lambdas are created like this:

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)

But unfortunately System.Func doesn't support params varargs, so I'll have to create a delegate instead:

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

Which is my answer for this challenge, and can be found in the linked TIO test code.


The only limitation is that inputting an actual object[] like f(new object[]{1,2,3}) will result in 3 instead of 1. f(new int[]{1,2,3}) will still result in 1, because it interprets the int[] as a single object. To have the object[] parameter be interpret as a single object as well it can be casted to an object like this: f((object)new object[]{1,2,3}).


I have to say, if there were ever an answer that made me support including lambda-related boilerplate in C# answers it would be this one... but it is definitely a valid solution.
Kamil Drakari

@KamilDrakari Maybe it indeed wasn't very clear what I did without opening the TIO-link, so I've added an explanation.
Kevin Cruijssen

1
@Taemyr I tried finding a solution, but unfortunately there is none for C# .NET, except for casting any object[] parameters to object, like this: f((object)new object[]{1,2,3});. There is no way to differentiate between f(new object[]{1,2,3}); and f(1,2,3); as far as I could find.
Kevin Cruijssen

1
this handles array parameters correctly for a huge penalty of bytes. There might be a more concise structure that can handle it, but it works in my testing.
Kamil Drakari

1
@KamilDrakari Hmm, but it fails for f(1, new object[]{1,2,3}) again though. Not sure if a solution for this behavior can be found.
Kevin Cruijssen

4

Dodos, 32 31 bytes

f
	dot i f dab
i
	
	dip dot dab

Try it online!

Uses Dennis' increment function.

Explanation

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|)

Alternatively, 32 bytes without recursion in target function (thanks @Leo)

	dot i
i
	dip dot dab dot
	i dab

Try it online!

Explanation

        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)

Here's another same-length solution Try it online! I can't seem to understand why yours works though, could you add an explanation please?
Leo

Hey, you added an explanation to my solution! I wanted one for yours, I know how mine works xD
Leo

1
@Leo sorry for late reply, idek what I'm doing, just copied Dennis' function, will try to understand asap. I had no idea how dodos works so I figured out what yours did first
ASCII-only

No worries, it was just a funny situation :)
Leo

@Leo ok so does my explanation make sense? (note: I'm on mobile so feel free to edit it to make it better lol)
ASCII-only

3

C++, 72 bytes

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

Saves bytes by only working with ints.


You can use sizeof....
L. F.

3

Rust, 57 bytes

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

Explanation:

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.
    };
}

Test:

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 bytes

<?=$argc-1;

Try it online: 1 input | 3 inputs


I'm not so sure about this one (and it's validity) since it is the count of arguments passed to call PHP.
Ismael Miguel

@IsmaelMiguel, see this consensus.
Shaggy

1
The question explicitly requires a function that returns the number, does not display it: "...write a function that takes a variable number of arguments and returns the number of arguments."
axiac

1
Re-quoting the question: "Using your language of choice, write a function that takes a variable number of arguments and returns the number of arguments it was called with.". Your code doesn't contain functions.
Ismael Miguel

@IsmaelMiguel, if that were indeed the case then many other solutions would also be invalidated. The norm is to allow solutions to be programmes or functions.
Shaggy

2

Batch, 50 49 bytes

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

No builtin in Batch, so we have to go old-school. Saved 1 byte thanks to @IsmaelMiguel. Outputs via exit code, or save 3 bytes if output via global variable is valid. Example of use in a full program:

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

I believe that this answer is answer goes (somewhat) against the rules. Batch has something somewhat close to functions. You can do something similar to :a|set r=0&for %%a in (%*)do set/ar+=1 (| = windows-style newline). This solution is 38 bytes. To execute it, do call :a <args> with a goto :eof before the function, being the value available inside the variable r. If you want to keep your solution, remove the /a on the first set, and remove those @.
Ismael Miguel

@IsmaelMiguel Like this? (Note: I didn't include the function name in the byte count, but I did include the function return, which seems reasonable, as there needs to be one somewhere.)
Neil

Yes, that's exactly it. Nice catch with the exit code! I was surprised to see that exitcodes can be larger than 255. An example is the list provided by Symantec: symantec.com/connect/articles/…
Ismael Miguel

2

x86 32-bit (i386) machine code function, 13 bytes

Calling convention: i386 System V (stack args), with a NULL pointer as a sentinel / terminator for the end-of-arg-list. (Clobbers EDI, otherwise complies with SysV).

C (and asm) don't pass type info to variadic functions, so the OP's description of passing integers or arrays with no explicit type info could only be implemented in a convention that passed some kind of struct / class object (or pointers to such), not bare integers on the stack. So I decided to assume that all the args were non-NULL pointers, and the caller passes a NULL terminator.

A NULL-terminated pointer list of args is actually used in C for functions like POSIX execl(3): int execl(const char *path, const char *arg, ... /* (char *) NULL */);

C doesn't allow int foo(...); prototypes with no fixed arg, but int foo(); means the same thing: args unspecified. (Unlike in C++ where it means int foo(void)). In any case, this is an asm answer. Coaxing a C compiler to call this function directly is interesting but not required.

nasm -felf32 -l/dev/stdout arg-count.asm with some comment lines removed.

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

The question shows that the function must be able to return 0, and I decided to follow that requirement by not including the terminating NULL pointer in the arg count. This does cost 1 byte, though. (For the 12-byte version, remove the LEA and uncomment the scasd outside the loop and the xchg, but not the dec edx. I used LEA because it costs the same as those other three instructions put together, but is more efficient, so the function is fewer uops.)

C caller for testing:

Built with:

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 is required even at -O0 to tell gcc to assume that functions clobber edi without saving/restoring it, because I used so many calls in one C statement (the printf call) that even -O0 was using EDI. It appears to be safe for gcc's main to clobber EDI from its own caller (in CRT code), on Linux with glibc, but otherwise it's totally bogus to mix/match code compiled with different -fcall-used-reg. There's no __attribute__ version of it to let us declare the asm functions with custom calling conventions different from the usual.

#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);
}

Two other versions also came in at 13 bytes: this one based on loopne returns a value that's too high by 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

This version uses rep scasd instead of a loop, but takes the arg count modulo 256. (Or capped at 256 if the upper bytes of ecx are 0 on entry!)

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

Amusingly, yet another version based on inc eax / pop edx / test edx,edx / jnz came in at 13 bytes. It's a callee-pops convention, which is never used by C implementations for variadic functions. (I popped the ret addr into ecx, and jmp ecx instead of ret. (Or push/ret to not break the return-address predictor stack).




Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.