Quantos argumentos foram passados?


33

Usando seu idioma de escolha, escreva uma função que use um número variável de argumentos e retorne o número de argumentos com os quais foi chamado.

Específicos:

  • Seu idioma precisa suportar funções de argumento variadas: algo que pode ser chamado que pega um número arbitrário de argumentos e retorna um valor.
  • Os parâmetros devem poder ser passados ​​individualmente. Isso significa que a passagem de uma matriz contaria apenas um parâmetro. Você pode usar uma matriz "todos os argumentos passados" se o seu idioma suportar; a restrição é sobre como a função é chamada.
  • O código que chama essa função não deve ser necessário para transmitir o número de argumentos em sua fonte . Se um compilador inserir o número de argumentos como parte de uma convenção de chamada, isso é permitido.
  • Os argumentos podem ser de qualquer tipo que você desejar. Você pode suportar apenas um único tipo (por exemplo, apenas o suporte intainda é válido), tipos arbitrários (qualquer tipo de argumento é permitido) ou qualquer combinação de tipos de argumentos (por exemplo, primeiro argumento é int, resto é uma sequência de caracteres).
  • Sua função pode ter um número máximo de argumentos (especialmente porque os recursos são finitos), mas deve suportar pelo menos 2 argumentos.

Amostras:

  • f() retorna 0
  • f(1)ou f("a")retorna1
  • f([1, 2, 3])retorna 1quando passa uma matriz, não 3 argumentos
  • f(1, 10)ou f(1, "a")retorna2

Como se trata de código-golfe, a solução vencedora é a que utiliza o menor número de bytes.


4
Não está totalmente claro (objetivamente) o que é uma "função", "valor de retorno" ou "argumentos variados". Por exemplo, a função Dodos seria considerada monádica ou variável?
usar o seguinte comando

24
@ user202729 Se o seu idioma não suportar funções, use outro idioma. Não é um requisito que todos os idiomas possam competir, parte do código do golfe é encontrar a ferramenta certa para o trabalho.
Sanchises

5
@ user202729 Não tenho problemas com o desafio ocasional destinado a idiomas tradicionais / de alto nível, assim como o desafio ocasional que só é possível em idiomas incomuns.
Sanchises

6
não sabia que tínhamos de resolver o problema da parada para características da linguagem de ter um claro desafio ....
Conor O'Brien

5
Se seu idioma não possui o conceito de argumentos / convenção de chamada, ele não se encaixa nos critérios de suporte a números arbitrários de argumentos.
Glenn Smith

Respostas:


15

Chamada binária Amstrad CPC Z80 de BASIC, 1 byte, codificado em hexadecimal

C9          : RET

(Também versões de 2 e 5 bytes, veja abaixo)

Após a entrada na chamada, o número de parâmetros passados ​​estará no Aregistro. O código simplesmente retorna imediatamente. Não há conceito de valores de retorno no Z80, apenas estados de entrada e saída. O valor é "lá" acessível no registro, pois o código não altera as condições de entrada, exceto PC(o contador do programa) e SP(o ponteiro da pilha). No entanto, o valor in Anão é acessível ao BASIC e é substituído quase imediatamente.

Exemplos:

CALL &8000, "Hello", "World"

A = 2

CALL &8000, 42

A = 1

CALL &8000

A = 0


Por solicitação, aqui está um código que torna o valor acessível em BASIC. Fiquei muito surpreso ao descobrir que isso poderia ser feito em apenas 5 bytes !:

O código da máquina:

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

Na entrada:

  • AF - o acumulador e os registros de sinalizadores (tratados como dois registros de 8 bits)
    • A contém o número de parâmetros passados, até o máximo de 32 parâmetros
    • Não tenho certeza do que está entrando F. Parece ter todos os sinalizadores RESET 0, exceto os dois sinalizadores indefinidos que são ambos 1. O Zsinalizador (zero) é SET 1se não houver parâmetros passados ​​no
  • BC
    • B- 32 menos o número de parâmetros ( A+ B= 32)
    • C - &FF
  • DE - O endereço do último parâmetro ou o endereço de chamada se nenhum parâmetro foi passado
  • HL - O endereço do primeiro byte após o comando BASIC tokenizado atualmente em execução (como um programa ou no modo de comando imediato)
  • IX - O endereço da pilha do ponteiro para o último parâmetro
  • IY - &0000

O código

  1. Lo Dé o endereço indicado por DEcom o valor emA
  2. INCrements DE
  3. XORs A(com A), dando&00
  4. Lo Dé o valor Apara o endereço indicado porDE
  5. RETurnas

Na saída:

  • Aé destruído (é sempre &00)
  • DE é destruído (é sempre mais alto do que na entrada)
  • Todos os outros registros são preservados

O básico

O Amstrad basic possui apenas três tipos de dados, além de matrizes simples. Por padrão, todas as variáveis ​​BASIC são REAL (assinadas, mantissa de 32 bits, expoente de 8 bits), as quais podem ser explicitadas !. Para um uso INTEGER (assinado, 16 bits) %e para um uso STRING (comprimento de cadeia de 1 byte, dados de caracteres de até 255 bytes, segurança binária) $:

  • x - REAL (implícito)
  • x! - REAL (explícito)
  • x% - INTEGER
  • x$ - CORDA

Você também pode usar DEFINT, DEFREALe DEFSTRcom uma única letra, ou um intervalo de duas letras simples para especificar o tipo padrão para todas as variáveis que começam com essa letra, semelhante ao Fortran.

  • DEFSTR a
  • DEFINT x-z

Agora:

  • a - STRING (implícito)
  • i - REAL (implícito)
  • x - INTEGER (implícito)
  • x$ - STRING (explícito)

O tipo mais fácil de trabalhar é o número inteiro. O código da máquina espera que o último parâmetro seja passado por endereço, não por valor, razão pela qual @é prefixado na variável. A variável de retorno é contada como um dos CALLparâmetros.

O código da máquina é chamado da seguinte forma no BASIC (assumindo que ele está carregado na memória no endereço &8000):

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

n% = 4

Isso sempre fornecerá o resultado correto, independentemente do valor inicial de n%.

Para uma versão de 2 bytes que preserva todos os registros de entrada:

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

n% = 4

Isso pula os três primeiros bytes e fornece apenas o resultado correto se o valor inicial de n%é 0- 255. Isso funciona porque o Z80 é pequeno-endian.

O parâmetro de retorno deve ser inicializado antes de ser passado, caso contrário, o BASIC emitirá um Improper argumenterro. Na imagem abaixo, estou imprimindo (com o atalho ?desde que também jogamos na demonstração!) Os valores de retorno imediatamente antes e depois da chamada para mostrar a alteração do valor. Estou usando o valor &FFFFporque essa é a representação binária de -1para um número inteiro assinado. Isso demonstra que o programa de 5 bytes grava corretamente os dois bytes, enquanto o programa de 2 bytes grava apenas o byte baixo e supõe que o byte alto já esteja &00.

insira a descrição da imagem aqui


Então, como a convenção de chamada que você está usando retorna valores? Se ele não os devolver no acumulador, ou a todos, sua resposta é basicamente inventar uma convenção de chamada personalizada que resolva o problema para você (adicionando um registro de retorno, em vez de passar um ponteiro onde você pode armazenar A, se for o caso). como você pode realmente fazê-lo no BASIC). Não que haja algo errado nisso, mas pode ser uma resposta mais interessante seguir uma convenção de convocação existente.
12138 Peter Cordes

@PeterCordes Nem o Amstrad BASIC nem o Z80 têm o conceito de escopos. Todos os valores são globais e são acessíveis imediatamente até serem destruídos. O valor de Aé o mesmo imediatamente após a RETinstrução. A vida útil de um valor Aé muito curta, pois é o acumulador. Não existe tal coisa x = CALL &8000, 42. Teria que ser CALL &8000, x, 42, e código Z80 extra, mas depois xseria 2, não 1.
CJ Dennis

Eu acho que é bom se você incluir o argumento de saída na contagem, caso contrário, há uma instrução de decremento de 1 byte, não existe? Eu estaria interessado em ver uma versão que era realmente utilizável do BASIC em vez de ser trivial.
Peter Cordes

1
@PeterCordes Done! Ah, a propósito, eu esqueci de mencionar não chamá-lo sem parâmetros, pois ele substituirá suas próprias duas primeiras instruções com &00s- NOPno-ops. Outro byte pode ser adicionado para torná-lo mais seguro, mas é claro que sem um parâmetro de retorno, ele não pode definir nada.
CJ Dennis

32

Java (JDK 10) , 11 bytes

a->a.length

Experimente online!


29
Java batendo Javscript é raro o suficiente para ser notado
The random guy

3
@Therandomguy, isso requer que algo como interface x{void f(Object...a);}seja definido, e esse lambda deve ser armazenado em uma variável desse tipo de interface ou ser passado para um método que espera esse tipo de interface, por isso não tenho certeza se isso conta para esse desafio (mesmo embora geralmente lambdas java são permitidos em desafios codegolf)
SamYonnou

3
@ Samyonnou Não há diferença com outras lambdas e, como você mencionou, as lambdas são boas .
Olivier Grégoire

@ OlivierGrégoire Eu sei que lambdas são permitidas, o que quero dizer é que, em comparação com o JavaScript, por exemplo, você precisa de muito mais código extra para configurá-lo, mesmo se estiver usando algo como REPL e evitar a necessidade de uma classe / método principal ( a necessidade de definir a interface é o que a distingue do JavaScript)
SamYonnou 11/04

@ OlivierGrégoire: Conheço Java, mas não o acompanhei. Eu estava interessado em ver o comentário de Sam sobre o que o clichê está sendo varrido para debaixo do tapete em uma resposta Java que permite que ele seja realmente muito curto. Concordo que isso deve ser permitido (mesmo que isso ofereça algo que você normalmente não obtém com as funções Java, certo, então não se trata apenas de uma redução no padrão, mas de uma contagem interna de argumentos). Além disso, ainda é interessante como resposta a "Java batendo JS".
Peter Cordes

25

JavaScript, 15 bytes

[].push.bind(0)

A Array.prototype.pushfunção pega qualquer número de argumentos, os adiciona à sua matriz e retorna o tamanho da matriz. Portanto, a pushfunção usada em uma matriz vazia retorna o número de argumentos fornecidos push.

f = [].push.bind(0)

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

f()
> 0

O .bind(0)simplesmente fornece à pushfunção um thisvalor fixo para que possa ser armazenada em uma variável. De fato, o identificador de 7 bytes [].pushpode ser usado literalmente (mas não atribuído) sem bind:

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

[].push()
> 0

19

JavaScript (ES6), 16 bytes

(...a)=>a.length


18

Haskell , 108 107 95 94 bytes

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

Experimente online!

Foi surpreendentemente difícil começar a trabalhar, mas me diverti tentando descobrir como implementar algo que é trivial em linguagens imperativas.


Droga, você me venceu. fé opcional se você disserz 0 é a função sem a ligação, então main = print $ ((z 0) pi 0 () [] :: Int)funciona.
Angs

E com isso quero dizer que os tipos funcionam quando usados ​​como uma função anônima, para que você possa remover tudo das duas últimas linhas, exceto z 0
Angs

Bom obrigado! Acontece que eu estava fazendo algo errado ao testar a função anônima. Tentei o seu exemplo e funcionou muito bem.
precisa saber é o seguinte

Eu acho que o ::Intdevem ser contados na contagem de bytes, uma vez que o tipo de resposta tem que ser declarado mais cedo ou mais tarde, como em main = print $ ((z 0 :: Double -> Integer -> () -> [a] -> (Int->Int->Int) -> IO () -> Int) pi 0 () [] (+) main). Eu também acho que isso funciona apenas durante o tempo de compilação, então algo como foldl(\a b->a b) (z 0) $ [1..5])::Intnão pode funcionar. De qualquer forma, isso é ótimo.
Angs

2
s/imperative/non-curry/
usar o seguinte comando


12

Zsh , 7 5 bytes

<<<$#

Experimente online!


Embora provavelmente deve ser acondicionada:f(){ echo $#; }
Muru

8
@ muru Isso parece bom como um programa completo para mim.
Neil

Embora, vejo agora que o OP quer apenas uma função ...
Neil

2
Os scripts de shell do @Neil agem exatamente como funções. O OP não esclarece o que é uma função, afirmo que meu envio é apenas uma função salva no disco.
Pavel

9

Brain-Flak , 6 bytes

Minha primeira publicação digna de solução Brain-Flak, acho que é a ferramenta certa para este trabalho:

([]<>)

Experimente online!

Explicação

Ao executar um programa Brain-Flak, inicialmente a pilha esquerda contém todos os argumentos. A partir daí, é simplesmente uma questão de:

(      -- 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 bytes

Tr[1^{##}]&

Experimente online!

Sugerido por JungHwan Min. Algumas restrições (a entrada deve ser retangular), mas não somos obrigados a lidar com entradas arbitrárias.

11 bytes

Length@!##&

Experimente online!

Outra solução de 11 bytes sugerida por Martin Ender. Isso parece erro quando não há uma entrada, mas ainda retorna o valor correto em todos os casos.

12 bytes

Length@{##}&

Experimente online!

Minha solução original.

No Mathematica, ##significa um número variável de argumentos em uma função. {e os }agrupa em uma lista e Length@ocupa o comprimento dessa lista. &no final, transforma isso em uma função real.


7

R , 30 bytes

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

Experimente online!


1
function(...)nargs()tem 20 bytes, mas usar length(...)foi minha abordagem inicial até pesquisar uma nargsfunção no estilo.
Giuseppe

@Giuseppe hmm, eu tentei converter o list(...)a um tão lógico sum()poderia ser usado, mas isso é complicado: /
JAD

1
Haha, não tente e isca-me para que o argumento;)
RoryT

1
@RoryT oh, na verdade, R docs dizem combinar. Nevermind: D
JAD

2
...length() faz a mesma coisa quelength(list(...))
Giuseppe

7

Bash, 12 bytes (graças a paxdiablo por salvar 4)

n()(echo $#)

Copie e cole em um prompt do bash. Em seguida, execute a função n no prompt:

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

2
Bem-vindo ao PPCG!
22818 Martin Ender

você pode apenas dizer que é um script "./n" e não uma função? então é só echo $#:, 7 bytes. (será então qualquer shell que você usa para iniciar o script "./n" com ou seja, você corre o bash então quando você:.? ./n arg1 ... argnele será interpretado pelo bash.)
Olivier Dulac

@Olivier Dulac O desafio diz claramente uma função.
Wastrel # 13/18

7

C ++ 14 (gcc) , 34 bytes

As generic variadic lambda function (C++14 required):

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

Try it online!

Previous (incorrect) answer: 32 bytes

It was missing the template<class...T> and (p)

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

6
C++14, C++11 has no generic lambdas.
Quentin

1
You could add the permissive flag to be able to remove the parenthesis around p (and -w to turn off the warning).
nwp

@nwp: wouldn't -fpermissive cost you the 12 bytes for that option, though? If it's not standard ISO C++ or GNU C++.
Peter Cordes

@PeterCordes It probably does and is intended to avoid having a trivial 0-byte solution for everything by passing the program via command line. I just didn't think about that here because it seems to not be abusive.
nwp

@Quentin fixed -> C++14
Bierpfurz


5

Octave, 9 bytes

@()nargin

Try it online!

Anonymous function taking any number of arguments (and silently discarding the lot), and outputs the number of arguments through the built-in nargin. This does not work in MATLAB, where you would need varargin to allow for arbitrary many arguments.



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



1

JavaScript, 35 bytes

f=
function(){return arguments.length}

console.log(f(2,5,4))


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.