Quanti argomenti sono stati passati?


33

Usando il linguaggio che preferisci, scrivi una funzione che accetta un numero variabile di argomenti e restituisce il numero di argomenti con cui è stata chiamata.

specifiche:

  • La tua lingua deve supportare le funzioni degli argomenti variabili: qualcosa che può essere chiamato che accetta un numero arbitrario di argomenti e restituisce un valore.
  • I parametri devono poter essere passati singolarmente. Ciò significa che il passaggio di un array conta solo per un parametro. È possibile utilizzare un array "tutti gli argomenti passati" se la lingua lo supporta; la restrizione è su come viene chiamata la funzione.
  • Il codice che chiama questa funzione non deve essere richiesto per passare il numero di argomenti nella sua fonte . Se un compilatore inserisce il numero di argomenti come parte di una convenzione di chiamata, ciò è consentito.
  • Gli argomenti possono essere di qualsiasi tipo tu voglia. È possibile supportare solo un singolo tipo (ad es. Solo il supporto intè ancora valido), tipi arbitrari (è consentito qualsiasi tipo di argomento) o qualsiasi combinazione di tipi di argomenti (ad esempio il primo argomento è int, il resto sono stringhe).
  • La tua funzione può avere un numero massimo di argomenti (soprattutto perché le risorse sono limitate), ma deve supportare almeno 2 argomenti.

Campioni:

  • f() ritorna 0
  • f(1)o f("a")ritorna1
  • f([1, 2, 3])restituisce 1quando viene passato un array, non 3 argomenti
  • f(1, 10)o f(1, "a")ritorna2

Trattandosi di code-golf, la soluzione vincente è quella che utilizza il minor numero di byte.


4
Non è del tutto chiaro (oggettivamente) che cos'è una "funzione", "valore di ritorno" o "argomenti variadici". Ad esempio, la funzione di Dodos sarebbe considerata monadica o variadica?
user202729

24
@ user202729 Se la tua lingua non supporta le funzioni, usa un'altra lingua. Non è un requisito che tutte le lingue possano competere, parte del golf del codice è trovare lo strumento giusto per il lavoro.
Sanchises,

5
@ user202729 Non ho problemi con la sfida occasionale rivolta alle lingue tradizionali / di alto livello, così come abbiamo la sfida occasionale che è possibile solo in lingue insolite.
Sanchises,

6
non sapevo che dovevamo risolvere il problema di interruzione delle caratteristiche del linguaggio per avere una chiara sfida ...
Conor O'Brien,

5
Se la tua lingua non ha il concetto di argomenti / convenzione di chiamata, allora non soddisfa i criteri di supporto di un numero arbitrario di argomenti.
Glenn Smith,

Risposte:


15

Amstrad CPC Z80 chiamata binaria da BASIC, 1 byte, codifica esadecimale

C9          : RET

(Anche versioni da 2 e 5 byte, vedi sotto)

Al momento dell'iscrizione alla chiamata, il numero di parametri passati sarà nel Aregistro. Il codice ritorna semplicemente immediatamente. Non esiste un concetto di valori di ritorno nella Z80, ma solo gli stati di entrata e di uscita. Il valore è "lì" accessibile nel registro poiché il codice non modifica alcuna condizione di input, ad eccezione diPC (il contatore del programma) e SP(il puntatore dello stack). Tuttavia, il valore in Anon è accessibile a BASIC e viene sovrascritto quasi immediatamente.

Esempi:

CALL &8000, "Hello", "World"

A = 2

CALL &8000, 42

A = 1

CALL &8000

A = 0


Su richiesta, ecco un codice che rende il valore accessibile in BASIC. Sono stato molto sorpreso di scoprire che poteva essere fatto in soli 5 byte !:

Il codice macchina:

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

All'entrata:

  • AF - i registri accumulatore e flag (trattati come due registri a 8 bit)
    • A contiene il numero di parametri passati, fino ad un massimo di 32 parametri
    • Non sono sicuro di cosa ci sia dentro F. Sembra avere tutti i flag RESET su 0, tranne i due flag indefiniti che sono entrambi 1. Il Zflag (zero) è impostato su 1se non sono stati passati parametri
  • BC
    • B- 32 meno il numero di parametri ( A+ B= 32)
    • C - &FF
  • DE - L'indirizzo dell'ultimo parametro o l'indirizzo chiamante se non sono stati passati parametri
  • HL - L'indirizzo del primo byte dopo il comando BASIC tokenizzato attualmente in esecuzione (come programma o in modalità comando immediato)
  • IX - L'indirizzo dello stack del puntatore all'ultimo parametro
  • IY - &0000

Il codice

  1. Lo Ds l'indirizzo indicato da DEcon il valore inA
  2. INCrements DE
  3. XORs A(con A), dando&00
  4. Lo Ds il valore Anell'indirizzo indicato daDE
  5. RETurne

All'uscita:

  • Aè distrutto (è sempre &00)
  • DE è distrutto (è sempre uno più alto rispetto all'ingresso)
  • Tutti gli altri registri vengono conservati

La base

Amstrad basic ha solo tre tipi di dati, oltre a semplici array. Per impostazione predefinita, tutte le variabili BASIC sono REAL (con segno, mantissa a 32 bit, esponente a 8 bit), che possono essere esplicitate con !. Per un uso INTEGER (con segno, 16 bit) %e per un STRING (lunghezza stringa di 1 byte, fino a 255 byte di dati carattere, sicurezza binaria) utilizzare $:

  • x - REAL (implicito)
  • x! - REAL (esplicito)
  • x% - NUMERO INTERO
  • x$ - STRING

È inoltre possibile utilizzare DEFINT, DEFREALe DEFSTRcon una sola lettera, o un intervallo di due lettere singole per specificare il tipo di default per tutte le variabili che iniziano con quella lettera, simile a FORTRAN.

  • DEFSTR a
  • DEFINT x-z

Adesso:

  • a - STRING (implicito)
  • i - REAL (implicito)
  • x - INTEGER (implicito)
  • x$ - STRING (esplicito)

Il tipo più semplice con cui lavorare è l'intero. Il codice macchina prevede che l'ultimo parametro sia passato per indirizzo, non per valore, motivo per cui @è prefissato la variabile. La variabile di ritorno viene conteggiata come uno dei CALLparametri s.

Il codice macchina si chiama come segue da BASIC (supponendo che sia caricato in memoria all'indirizzo &8000):

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

n% = 4

Questo darà sempre il risultato corretto, indipendentemente dal valore iniziale di n%.

Per una versione a 2 byte che conserva tutti i registri di input:

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

n% = 4

Ciò salta i primi tre byte e fornisce il risultato corretto solo se il valore iniziale di n%è 0- 255. Questo funziona perché lo Z80 è little-endian.

Il parametro return deve essere inizializzato prima di essere passato, altrimenti BASIC genererà un Improper argumenterrore. Nell'immagine seguente, sto stampando (con la scorciatoia ?da quando ho giocato anche a golf la dimostrazione!) I valori di ritorno immediatamente prima e dopo la chiamata per mostrare la modifica del valore. Sto usando il valore &FFFFperché questa è la rappresentazione binaria di -1per un numero intero con segno. Ciò dimostra che il programma a 5 byte scrive correttamente entrambi i byte, mentre il programma a 2 byte scrive solo il byte basso e presuppone che il byte alto sia già &00.

enter image description here


Quindi, in che modo la convenzione di chiamata che stai utilizzando restituisce valori? Se non li restituisce nell'accumulatore, o almeno, la tua risposta sta fondamentalmente inventando una convenzione di chiamata personalizzata che risolva il problema per te (aggiungendo un registro di ritorno, invece di passare un puntatore in cui è possibile archiviare A, se quello è come potresti effettivamente farlo da BASIC). Non che ci sia qualcosa di sbagliato in questo, ma potrebbe essere una risposta più interessante seguire una convenzione di calliing esistente.
Peter Cordes,

@PeterCordes Né Amstrad BASIC né Z80 hanno il concetto di ambiti. Tutti i valori sono globali e sono immediatamente accessibili fino alla distruzione. Il valore di Aè lo stesso immediatamente dopo l' RETistruzione. La durata di un valore Aè molto breve poiché è l'accumulatore. Non esiste x = CALL &8000, 42. Esso dovrebbe essere CALL &8000, x, 42, e codice extra Z80, ma poi xsarebbe stato 2, non è 1.
CJ Dennis,

Penso che vada bene se includi l'output arg nel conteggio, altrimenti c'è un'istruzione di decremento di 1 byte non è lì? Sarei interessato a vedere una versione che era effettivamente utilizzabile da BASIC invece di essere banale.
Peter Cordes,

1
@PeterCordes Done! Oh, a proposito, ho dimenticato di menzionare di non chiamarlo senza parametri in quanto sovrascriverà le sue prime due istruzioni con &00s - NOPno-ops. Un altro byte può essere aggiunto per renderlo più sicuro, ma ovviamente senza un parametro di ritorno non può impostare nulla.
CJ Dennis,

32

Java (JDK 10) , 11 byte

a->a.length

Provalo online!


29
Java che batte Javscript è raro da notare
Il ragazzo casuale

3
@Therandomguy richiede qualcosa di simile interface x{void f(Object...a);}da definire, e questo lambda deve essere memorizzato in una variabile di quel tipo di interfaccia, o essere passato a un metodo in attesa di quel tipo di interfaccia, quindi non sono davvero sicuro che valga per questa sfida (anche anche se di solito java lambdas è ammesso nelle sfide di codegolf)
SamYonnou,

3
@SamYonnou Non c'è differenza con altri lambda e, come hai detto, gli lambda vanno bene .
Olivier Grégoire,

@ OlivierGrégoire So che sono consentiti lambdas, il mio punto era che rispetto ad JavaScript, ad esempio, hai bisogno di molto più codice aggiuntivo per configurarlo, anche se stai usando qualcosa come REPL ed eviti la necessità di una classe / metodo principale ( la necessità di definire l'interfaccia è ciò che la distingue da JavaScript)
SamYonnou

@ OlivierGrégoire: conosco un po 'di Java ma non ci sono riuscito affatto. Ero interessato a vedere il commento di Sam su quale scaldabagno viene spazzato sotto il tappeto in una risposta Java che lo rende davvero breve. Sono d'accordo che dovrebbe essere consentito (anche se ti dà qualcosa che normalmente non ottieni con le funzioni Java, giusto, quindi non è solo una riduzione della piastra di cottura, ma ti dà il conteggio degli arg-built-in). Oltre a ciò, è ancora interessante come risposta a "Java che batte JS".
Peter Cordes,

25

JavaScript, 15 byte

[].push.bind(0)

La Array.prototype.pushfunzione accetta qualsiasi numero di argomenti, li aggiunge alla sua matrice e restituisce la dimensione della matrice. Pertanto, la pushfunzione utilizzata su un array vuoto restituisce il numero di argomenti forniti a push.

f = [].push.bind(0)

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

f()
> 0

Il .bind(0)dà semplicemente la pushfunzione di un determinato thisvalore in modo che può essere memorizzato in una variabile. In effetti, l'identificatore a 7 byte [].pushpuò essere usato letteralmente (ma non assegnato) senza bind:

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

[].push()
> 0

19

JavaScript (ES6), 16 byte

(...a)=>a.length


18

Haskell , 108 107 95 94 byte

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

Provalo online!

È stato sorprendentemente difficile farlo funzionare, ma mi sono divertito cercando di scoprire come implementare qualcosa che è banale in linguaggi imperativi.


Accidenti, mi hai battuto. fè facoltativo se si dice che z 0è la funzione senza associazione, quindi main = print $ ((z 0) pi 0 () [] :: Int)funziona.
Angs

E con questo intendo che i tipi funzionano quando vengono usati come una funzione anonima in modo da poter effettivamente rimuovere tutto dalle ultime due righe trannez 0
Angs

Bene grazie! Ho scoperto che stavo facendo qualcosa di sbagliato quando stavo testando la funzione anonima. Ho provato il tuo esempio e ha funzionato bene.
user9549915

Immagino che ::Intdovrebbe essere conteggiato nel conteggio dei byte, poiché il tipo di risposta deve essere dichiarato prima o poi, come in main = print $ ((z 0 :: Double -> Integer -> () -> [a] -> (Int->Int->Int) -> IO () -> Int) pi 0 () [] (+) main). Penso anche che questo foldl(\a b->a b) (z 0) $ [1..5])::Intfunzioni solo durante il tempo di compilazione, quindi qualcosa come non può funzionare. Ad ogni modo, questa è roba fantastica.
Angs

2
s/imperative/non-curry/
user202729


12

Zsh , 7 5 byte

<<<$#

Provalo online!


Anche se probabilmente dovrebbe essere avvolto:f(){ echo $#; }
muru

8
@muru Mi sembra un programma completo.
Neil,

Tuttavia, vedo ora che l'OP vuole solo una funzione ...
Neil,

2
Gli script della shell @Neil si comportano esattamente come le funzioni. OP non chiarisce che cos'è una funzione, presumo che la mia presentazione sia solo una funzione salvata sul disco.
Pavel,

9

Brain-Flak , 6 byte

La mia prima soluzione Brain-Flak degna di pubblicazione, credo sia lo strumento giusto per questo lavoro:

([]<>)

Provalo online!

Spiegazione

Quando si esegue un programma Brain-Flak, inizialmente lo stack di sinistra contiene tutti gli argomenti. Da lì si tratta semplicemente di:

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

Tr[1^{##}]&

Provalo online!

Suggerito da JungHwan Min. Alcune restrizioni (l'input deve essere rettangolare) ma non siamo tenuti a gestire input arbitrari.

11 byte

Length@!##&

Provalo online!

Un'altra soluzione a 11 byte suggerita da Martin Ender. Questo sembra errore quando non c'è un input ma restituisce comunque il valore corretto in tutti i casi.

12 byte

Length@{##}&

Provalo online!

La mia soluzione originale.

In Mathematica ##sta per un numero variabile di argomenti in una funzione. {e }li avvolge in un elenco e Length@occupa la lunghezza di questo elenco. &alla fine trasforma questo in una funzione reale.


7

R , 30 byte

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

Provalo online!


1
function(...)nargs()è di 20 byte, ma l'utilizzo è length(...)stato il mio approccio iniziale fino a quando ho cercato su Google una nargsfunzione simile.
Giuseppe,

@Giuseppe hmm, ho provato a convertire il list(...)logico in un modo logico, quindi sum()potrebbe essere usato, ma è difficile: /
JAD

1
Ahah, non tentare di
indurmi

1
@RoryT oh in realtà, i documenti R dicono che combinano. Nevermind: D
JAD

2
...length() fa la stessa cosa dilength(list(...))
Giuseppe,

7

Bash, 12 byte (grazie a paxdiablo per il salvataggio di 4)

n()(echo $#)

Copia e incolla a un prompt di bash. Quindi eseguire la funzione n dal prompt:

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

2
Benvenuti in PPCG!
Martin Ender,

puoi solo dire che è uno script "./n" e non una funzione? allora è solo echo $#:, 7 byte. (sarà quindi qualsiasi shell che usi per lanciare lo script "./n" con. cioè, esegui bash? allora quando tu: ./n arg1 ... argnsarà interpretato da bash.)
Olivier Dulac

@Olivier Dulac La sfida dice chiaramente una funzione.
Wastrel,

7

C ++ 14 (gcc) , 34 byte

Come funzione lambda variadica generica (richiesto C ++ 14):

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

Provalo online!

Risposta precedente (errata): 32 byte

Mancava il template<class...T>e(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))


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.