¿Cuántos argumentos fueron pasados?


33

Usando el lenguaje de su elección, escriba una función que tome un número variable de argumentos y devuelva el número de argumentos con los que se llamó.

Detalles específicos:

  • Su lenguaje debe admitir funciones de argumento variadic: algo invocable que toma un número arbitrario de argumentos y devuelve un valor.
  • Los parámetros deben poder pasarse individualmente. Esto significa que pasar una matriz solo contaría para un parámetro. Puede usar una matriz "todos los argumentos pasados" si su idioma lo admite; la restricción está en cómo se llama la función.
  • El código que llama a esta función no debe ser requerido para pasar el número de argumentos en su fuente . Si un compilador inserta el número de argumentos como parte de una convención de llamada, eso está permitido.
  • Los argumentos pueden ser de cualquier tipo que desee. Puede admitir solo un tipo (por ejemplo, solo el soporte intsigue siendo válido), tipos arbitrarios (se permite cualquier tipo de argumento) o cualquier combinación de tipos de argumento (por ejemplo, el primer argumento es int, el resto son cadenas).
  • Su función puede tener un número máximo de argumentos (especialmente porque los recursos son finitos), pero debe admitir al menos 2 argumentos.

Muestras:

  • f() devoluciones 0
  • f(1)o f("a")vuelve1
  • f([1, 2, 3])devuelve 1cuando se pasa una matriz, no 3 argumentos
  • f(1, 10)o f(1, "a")vuelve2

Como se trata de código de golf, la solución ganadora es la que utiliza la menor cantidad de bytes.


44
No está del todo claro (objetivamente) qué es una "función", "valor de retorno" o "argumentos variables". Por ejemplo, ¿se consideraría la función de Dodos como monádica o variable?
user202729

24
@ user202729 Si su idioma no admite funciones, use otro idioma. No es un requisito que todos los idiomas puedan competir, parte del golf de código es encontrar la herramienta adecuada para el trabajo.
Sanchises

55
@ user202729 No tengo problemas con el desafío ocasional dirigido a los idiomas tradicionales / de alto nivel, al igual que tenemos el desafío ocasional que solo es posible en idiomas inusuales.
Sanchises

66
no sabía que teníamos que resolver el problema de las características del lenguaje para tener un desafío claro ...
Conor O'Brien

55
Si su lenguaje no tiene el concepto de argumentos / convención de llamada, entonces no se ajusta a los criterios de soportar números arbitrarios de argumentos.
Glenn Smith

Respuestas:


15

Llamada binaria Amstrad CPC Z80 desde BASIC, 1 byte, codificado hexadecimal

C9          : RET

(También versiones de 2 y 5 bytes, ver más abajo)

Al ingresar a la llamada, el número de parámetros pasados ​​estará en el Aregistro. El código simplemente regresa de inmediato. No hay un concepto de valores de retorno en el Z80, solo estados de entrada y salida. El valor solo está "allí" accesible en el registro ya que el código no cambia las condiciones de entrada, excepto PC(el contador del programa) y SP(el puntero de la pila). Sin embargo, el valor en Ano es accesible para BASIC y se sobrescribe casi de inmediato.

Ejemplos:

CALL &8000, "Hello", "World"

A = 2

CALL &8000, 42

A = 1

CALL &8000

A = 0


A pedido, aquí hay un código que hace que el valor sea accesible en BASIC. ¡Me sorprendió mucho descubrir que se podía hacer en solo 5 bytes !:

El código de la máquina:

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

En la entrada:

  • AF - el acumulador y los registros de banderas (tratados como dos registros de 8 bits)
    • A contiene el número de parámetros pasados, hasta el máximo de 32 parámetros
    • No estoy seguro de qué hay F. Parece que todas las banderas RESET 0, excepto las dos banderas indefinidas que son ambas 1. El Zindicador (cero) se establece en 1si no se pasaron parámetros en
  • BC
    • B- 32 menos el número de parámetros ( A+ B= 32)
    • C - &FF
  • DE - La dirección del último parámetro, o la dirección de llamada si no se pasaron parámetros
  • HL - La dirección del primer byte después del comando BASIC tokenizado que se está ejecutando actualmente (ya sea como programa o en modo de comando inmediato)
  • IX - La dirección de la pila del puntero al último parámetro
  • IY - &0000

El código

  1. Loa Ds la dirección apuntada por DEcon el valor enA
  2. INCrements DE
  3. XORs A(con A), dando&00
  4. Loa Ds el valor Ade la dirección señalada porDE
  5. RETurnas

A la salida:

  • Ase destruye (siempre es &00)
  • DE se destruye (siempre es uno más alto que en la entrada)
  • Todos los demás registros se conservan.

Lo básico

Amstrad basic solo tiene tres tipos de datos, más matrices simples. Por defecto, todas las variables BÁSICAS son REALES (con signo, mantisa de 32 bits, exponente de 8 bits), que se pueden hacer explícitas con !. Para un uso INTEGER (firmado, 16 bits) %y para un STRING (longitud de cadena de 1 byte, hasta 255 bytes de datos de caracteres, binario seguro) use $:

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

También puede usar DEFINT, DEFREALy DEFSTRcon una sola letra, o un rango de dos letras individuales para especificar el tipo predeterminado para todas las variables que comienzan con esa letra, similar a FORTRAN.

  • DEFSTR a
  • DEFINT x-z

Ahora:

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

El tipo más fácil para trabajar es el entero. El código de máquina espera que el último parámetro pase por dirección, no por valor, razón por la cual @está prefijado a la variable. La variable de retorno se cuenta como uno de los CALLparámetros s.

El código de máquina se llama como sigue desde BASIC (suponiendo que se cargue en la memoria en la dirección &8000):

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

n% = 4

Esto siempre dará el resultado correcto, independientemente del valor inicial de n%.

Para una versión de 2 bytes que conserva todos los registros de entrada:

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

n% = 4

Esto omite los primeros tres bytes y solo da el resultado correcto si el valor inicial de n%es 0- 255. Esto funciona porque el Z80 es little-endian.

El parámetro de retorno debe inicializarse antes de pasarlo; de lo contrario, BASIC arrojará un Improper argumenterror. En la imagen a continuación, estoy imprimiendo (¡con el atajo ?ya que también jugué la demostración!) Los valores de retorno inmediatamente antes y después de la llamada para mostrar el cambio de valor. Estoy usando el valor &FFFFporque esa es la representación binaria de -1un entero con signo. Esto demuestra que el programa de 5 bytes escribe correctamente ambos bytes, mientras que el programa de 2 bytes solo escribe el byte bajo y supone que el byte alto ya está &00.

ingrese la descripción de la imagen aquí


Entonces, ¿cómo funciona la convención de llamada que está utilizando valores de retorno? Si no los devuelve en el acumulador, o en absoluto, entonces su respuesta es básicamente inventar una convención de llamada personalizada que resuelva el problema por usted (agregando un registro de retorno, en lugar de pasar un puntero donde pueda almacenar A, si eso es cómo podría hacerlo desde BASIC). No es que haya nada de malo en eso, pero podría ser una respuesta más interesante seguir una convención de llamadas existente.
Peter Cordes

@PeterCordes Ni Amstrad BASIC ni el Z80 tienen el concepto de ámbitos. Todos los valores son globales y son accesibles de inmediato hasta que se destruyan. El valor de Aes el mismo inmediatamente después de la RETinstrucción. La vida útil de un valor en Aes muy corta ya que es el acumulador. No hay tal cosa como x = CALL &8000, 42. Tendría que ser CALL &8000, x, 42, y un código Z80 adicional, pero luego xsería 2, no 1.
CJ Dennis

Creo que está bien si incluye el argumento de salida en el recuento, de lo contrario, hay una instrucción de disminución de 1 byte, ¿no? Me interesaría ver una versión que fuera realmente utilizable desde BASIC en lugar de ser trivial.
Peter Cordes

1
@PeterCordes Hecho! Ah, por cierto, olvidé mencionar no llamarlo sin parámetros, ya que sobrescribirá sus dos primeras instrucciones con &00s - NOPno-ops. Se puede agregar otro byte para hacerlo más seguro, pero, por supuesto, sin un parámetro de retorno no se puede establecer nada.
CJ Dennis

32

Java (JDK 10) , 11 bytes

a->a.length

Pruébalo en línea!


29
Java superando a Javscript es raro que se note
The random guy

3
@Therandomguy esto requiere algo como interface x{void f(Object...a);}ser definido, y este lambda debe almacenarse en una variable de ese tipo de interfaz, o pasar a un método que espere ese tipo de interfaz, por lo que no estoy realmente seguro de que cuente para este desafío (incluso aunque generalmente se permiten las lambdas de Java en los desafíos de codegolf)
SamYonnou

3
@SamYonnou No hay diferencia con otras lambdas, y como mencionaste, las lambdas están bien .
Olivier Grégoire

@ OlivierGrégoire Sé que las lambdas están permitidas, mi punto fue que, en comparación con JavaScript, por ejemplo, necesitas mucho más código adicional para configurarlo, incluso si estás usando algo como REPL y evitas la necesidad de una clase / método principal ( la necesidad de definir la interfaz es lo que la distingue de JavaScript)
SamYonnou

@ OlivierGrégoire: Conozco algunos Java, pero no los he mantenido en absoluto. Me interesó ver el comentario de Sam sobre qué repetitivo está siendo barrido debajo de la alfombra en una respuesta de Java que permite que sea realmente breve. Estoy de acuerdo en que debería permitirse (a pesar de que le da algo que normalmente no obtiene con las funciones de Java, correcto, por lo que no es solo una reducción repetitiva, también le brinda un conteo de argumentos incorporado). Además de eso, sigue siendo interesante como respuesta a "Java beating JS".
Peter Cordes

25

JavaScript, 15 bytes

[].push.bind(0)

La Array.prototype.pushfunción toma cualquier número de argumentos, los agrega a su matriz y devuelve el tamaño de la matriz. Por lo tanto, la pushfunción utilizada en una matriz vacía devuelve el número de argumentos suministrados push.

f = [].push.bind(0)

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

f()
> 0

El .bind(0)simplemente le da a la pushfunción un thisvalor fijo para que pueda almacenarse en una variable. De hecho, el identificador de 7 bytes [].pushse puede usar literalmente (pero no asignado) sin bind:

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

[].push()
> 0


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

Pruébalo en línea!

Esto fue sorprendentemente difícil de conseguir, pero me divertí tratando de descubrir cómo implementar algo que es trivial en los idiomas imperativos.


Maldición, me ganaste. fes opcional si dice que z 0es la función sin el enlace, por lo que main = print $ ((z 0) pi 0 () [] :: Int)funciona.
Angs

Y con eso quiero decir que los tipos funcionan cuando se usan como una función anónima, por lo que puedes eliminar todo de las últimas dos filas exceptoz 0
Angs

¡Genial gracias! Resulta que estaba haciendo algo mal cuando estaba probando la función anónima. Intenté su ejemplo y funcionó bien.
user9549915

Supongo que ::Intdebe contarse en el recuento de bytes, ya que el tipo de respuesta debe declararse tarde o temprano, como en main = print $ ((z 0 :: Double -> Integer -> () -> [a] -> (Int->Int->Int) -> IO () -> Int) pi 0 () [] (+) main). También creo que esto funciona solo durante el tiempo de compilación, por lo que algo como foldl(\a b->a b) (z 0) $ [1..5])::Intno puede funcionar. De cualquier manera, esto es genial.
Angs

2
s/imperative/non-curry/
user202729


12

Zsh , 7 5 bytes

<<<$#

Pruébalo en línea!


Aunque probablemente debería estar envuelto:f(){ echo $#; }
muru

8
@muru Eso me parece un programa completo.
Neil

Aunque, ahora veo que el OP solo quiere una función ...
Neil

2
Los scripts de shell @Neil actúan exactamente como las funciones. OP no aclara qué es una función, afirmo que mi envío es solo una función guardada en el disco.
Pavel

9

Brain-Flak , 6 bytes

Mi primera publicación digna de solución de Brain-Flak, creo que es la herramienta adecuada para este trabajo:

([]<>)

Pruébalo en línea!

Explicación

Al ejecutar un programa Brain-Flak, inicialmente la pila izquierda contiene todos los argumentos. A partir de ahí, se trata simplemente 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^{##}]&

Pruébalo en línea!

Sugerido por JungHwan Min. Algunas restricciones (la entrada debe ser rectangular) pero no estamos obligados a manejar entradas arbitrarias.

11 bytes

Length@!##&

Pruébalo en línea!

Otra solución de 11 bytes sugerida por Martin Ender. Esto parece un error cuando no hay una entrada pero aún así devuelve el valor correcto en todos los casos.

12 bytes

Length@{##}&

Pruébalo en línea!

Mi solución original

En Mathematica ##significa un número variado de argumentos en una función. {y los }envuelve en una lista y Length@toma la longitud de esta lista. &al final convierte esto en una función real.


7

R , 30 bytes

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

Pruébalo en línea!


1
function(...)nargs()es de 20 bytes, pero el uso length(...)fue mi enfoque inicial hasta que busqué en Google una nargsfunción similar.
Giuseppe

@Giuseppe hmm, traté de convertirlo list(...)en lógico para sum()poder usarlo, pero eso es complicado: /
JAD

1
Jaja, no intentes
provocarme

1
@RoryT oh, en realidad, los documentos R dicen combinar. No importa: D
JAD

2
...length() hace lo mismo comolength(list(...))
Giuseppe

7

Bash, 12 bytes (gracias a paxdiablo por guardar 4)

n()(echo $#)

Copie y pegue en un indicador de bash. Luego ejecute la función n desde el indicador:

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

2
Bienvenido a PPCG!
Martin Ender

¿puedes decir que es un script "./n" y no una función? entonces es solo: echo $#7 bytes. (será entonces cualquier shell que use para iniciar el script "./n" con. ej., ejecuta bash? luego cuando: ./n arg1 ... argnserá interpretado por bash.)
Olivier Dulac

@Olivier Dulac El desafío dice claramente una función.
Wastrel

7

C ++ 14 (gcc) , 34 bytes

Como función lambda variadic genérica (se requiere C ++ 14):

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

Pruébalo en línea!

Respuesta anterior (incorrecta): 32 bytes

Faltaba el template<class...T>y(p)

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

66
C ++ 14, C ++ 11 no tiene lambdas genéricas.
Quentin


@nwp: ¿no le -fpermissivecostaría los 12 bytes para esa opción? Si no es estándar ISO C ++ o GNU C ++.
Peter Cordes

@PeterCordes Probablemente lo hace y está destinado a evitar tener una solución trivial de 0 bytes para todo al pasar el programa a través de la línea de comandos. Simplemente no pensé en eso aquí porque parece no ser abusivo.
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))


Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.