Combien d'arguments ont été passés?


33

En utilisant la langue de votre choix, écrivez une fonction qui prend un nombre variable d’arguments et renvoie le nombre d’arguments avec lesquels elle a été appelée.

Détails:

  • Votre langue doit prendre en charge des fonctions d’argument variadique: un objet appelable qui prend un nombre arbitraire d’arguments et renvoie une valeur.
  • Les paramètres doivent pouvoir être passés individuellement. Cela signifie que passer un tableau ne compterait que pour un paramètre. Vous pouvez utiliser un tableau "tous les arguments passés" si votre langue le supporte; la restriction est sur la façon dont la fonction est appelée.
  • Le code qui appelle cette fonction ne doit pas être obligé de transmettre le nombre d'arguments dans son source . Si un compilateur insère le nombre d'arguments dans le cadre d'une convention d'appel, cela est autorisé.
  • Les arguments peuvent être de tout type que vous voulez. Vous ne pouvez prendre en charge qu'un seul type (par exemple, seule la prise en charge intest toujours valide), des types arbitraires (tout type d'argument est autorisé) ou toute combinaison de types d'arguments (par exemple, le premier argument est int, restes sont des chaînes).
  • Votre fonction peut avoir un nombre maximum d'arguments (surtout depuis que les ressources sont finies), mais doit supporter au moins 2 arguments.

Échantillons:

  • f() résultats 0
  • f(1)ou f("a")retourne1
  • f([1, 2, 3])retourne 1comme un tableau, pas 3 arguments
  • f(1, 10)ou f(1, "a")retourne2

S'agissant de code-golf, la solution gagnante est celle qui utilise le moins d'octets.


4
Ce n'est pas tout à fait clair (objectivement) ce qu'est une "fonction", une "valeur de retour" ou des "arguments variadiques". Par exemple, la fonction Dodos serait-elle considérée comme monadique ou variadique?
user202729

24
@ user202729 Si votre langue ne prend pas en charge les fonctions, utilisez une autre langue. Il n’est pas nécessaire que toutes les langues soient en concurrence, le golf de code consiste en partie à trouver le bon outil pour le poste.
Sanchises

5
@ user202729 Je n'ai aucun problème avec le défi occasionnel destiné aux langues traditionnelles / de haut niveau, tout comme nous avons le défi occasionnel qui n'est possible que dans des langues inhabituelles.
Sanchises

6
Je ne savais pas que nous devions résoudre le problème critique des caractéristiques linguistiques pour avoir un défi clair ....
Conor O'Brien

5
Si votre langue n'a pas le concept d'arguments / convention d'appel, elle ne répond pas aux critères de prise en charge d'un nombre arbitraire d'arguments.
Glenn Smith

Réponses:


15

Appel binaire Amstrad CPC Z80 de BASIC, 1 octet, codé en hexadécimal

C9          : RET

(Également versions 2 et 5 octets, voir ci-dessous)

Lors de l'entrée dans l'appel, le nombre de paramètres transmis sera dans le Aregistre. Le code revient simplement immédiatement. Il n'y a pas de concept de valeurs de retour dans le Z80, juste des états d'entrée et de sortie. La valeur est simplement "là" accessible dans le registre puisque le code ne modifie aucune condition d'entrée, sauf pour PC(le compteur de programme) et SP(le pointeur de pile). Cependant, la valeur de An'est pas accessible à BASIC et est écrasée presque immédiatement.

Exemples:

CALL &8000, "Hello", "World"

A = 2

CALL &8000, 42

A = 1

CALL &8000

A = 0


Sur demande, voici un code rendant la valeur accessible en BASIC. J'ai été très surpris de constater que cela pouvait être fait en seulement 5 octets !:

Le code machine:

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

En entrée:

  • AF - les registres d'accumulateur et de drapeaux (traités comme deux registres de 8 bits)
    • A contient le nombre de paramètres passés, jusqu'à un maximum de 32 paramètres
    • Je ne suis pas sûr de ce qu'il y a dedans F. Il semble que tous les indicateurs RESET soient réinitialisés 0, à l'exception des deux indicateurs non définis qui sont tous les deux 1. L' Zindicateur (zéro) est réglé sur SET 1si aucun paramètre n'a été transmis
  • BC
    • B- 32 moins le nombre de paramètres ( A+ B= 32)
    • C - &FF
  • DE - L'adresse du dernier paramètre ou l'adresse du demandeur si aucun paramètre n'a été passé
  • HL - l'adresse du premier octet après l'exécution de la commande BASIC à jeton (soit en tant que programme, soit en mode de commande immédiate)
  • IX - l'adresse de pile du pointeur sur le dernier paramètre
  • IY - &0000

Le code

  1. Loa Dl'adresse indiquée par DEavec la valeur dansA
  2. INCrements DE
  3. XORs A(avec A), donnant&00
  4. Loa Dla valeur Ade l'adresse indiquée parDE
  5. RETurnes

En sortie:

  • Aest détruit (c'est toujours &00)
  • DE est détruit (il est toujours supérieur à l'entrée)
  • Tous les autres registres sont conservés

Les bases

Amstrad basic n'a que trois types de données, plus des tableaux simples. Par défaut, toutes les variables BASIC sont REAL (signées, mantisse 32 bits, exposant 8 bits), qui peuvent être explicites avec !. Pour une utilisation INTEGER (signée, 16 bits) %et pour une chaîne STRING (longueur de chaîne sur 1 octet, données caractères jusqu'à 255 octets, sécurité binaire), utilisez $:

  • x - REAL (implicite)
  • x! - REAL (explicite)
  • x% - INTEGER
  • x$ - CHAÎNE

Vous pouvez également utiliser DEFINT, DEFREALet DEFSTRavec une seule lettre, ou une plage de deux lettres simples pour spécifier le type par défaut de toutes les variables commençant par cette lettre, similaire à FORTRAN.

  • DEFSTR a
  • DEFINT x-z

À présent:

  • a - STRING (implicite)
  • i - REAL (implicite)
  • x - INTEGER (implicite)
  • x$ - STRING (explicite)

Le type le plus facile à utiliser est le nombre entier. Le code machine attend que le dernier paramètre soit transmis par adresse, et non par valeur, raison @pour laquelle la variable est préfixée. La variable de retour est comptée comme un des CALLparamètres.

Le code machine est appelé comme suit à partir de BASIC (en supposant qu’il soit chargé en mémoire à l’adresse &8000):

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

n% = 4

Cela donnera toujours le résultat correct, quelle que soit la valeur initiale de n%.

Pour une version à 2 octets qui conserve tous les registres d'entrée:

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

n% = 4

Ce saute les trois premiers octets, et ne donne que le résultat correct si la valeur initiale n%est 0- 255. Cela fonctionne parce que le Z80 est un peu endian.

Le paramètre de retour doit être initialisé avant d’être passé, sinon BASIC renverra une Improper argumenterreur. Dans l'image ci-dessous, je suis en ?train d' imprimer (avec le raccourci depuis que j'ai aussi joué à la démonstration!) Les valeurs de retour immédiatement avant et après l'appel pour montrer que la valeur change. J'utilise la valeur &FFFFparce que c'est la représentation binaire de -1pour un entier signé. Cela montre que le programme de 5 octets écrit correctement les deux octets, alors que le programme de 2 octets n'écrit que l'octet de poids faible et suppose que l'octet de poids fort existe déjà &00.

enter image description here


Alors, comment la convention d'appel que vous utilisez renvoie-t-elle des valeurs? S'il ne les renvoie pas ou pas du tout dans l'accumulateur, votre réponse consiste à inventer une convention d'appel personnalisée qui résout le problème pour vous (en ajoutant un registre de retour, au lieu de passer un pointeur où vous pouvez enregistrer A, si c'est le cas. comment vous pourriez réellement le faire à partir de BASIC). Cela n’est pas faux, mais il serait peut-être plus intéressant de suivre une convention d’appel existante.
Peter Cordes

@PeterCordes Ni Amstrad BASIC ni le Z80 n'ont le concept de scope. Toutes les valeurs sont globales et sont immédiatement accessibles jusqu'à leur destruction. La valeur de Aest la même immédiatement après l' RETinstruction. La durée de vie d'une valeur Aest très courte car c'est l'accumulateur. Il n'y a rien de tel que x = CALL &8000, 42. Il faudrait que ce soit CALL &8000, x, 42un code Z80 supplémentaire, mais ce xne serait 2pas le cas 1.
CJ Dennis

Je pense que c'est bien si vous incluez l'argument de sortie dans count, sinon il y a une instruction de décrémentation sur 1 octet, non? Je serais intéressé de voir une version réellement utilisable de BASIC au lieu d’être triviale.
Peter Cordes

1
@PeterCordes Done! Oh, au fait, j’ai oublié de ne pas l’appeler sans paramètre car cela écraserait ses deux premières instructions avec &00s - NOPno-ops. Un autre octet peut être ajouté pour le rendre plus sûr, mais bien sûr, sans paramètre de retour, il ne peut rien définir.
CJ Dennis

32

Java (JDK 10) , 11 octets

a->a.length

Essayez-le en ligne!


29
Java battant Javscript est assez rare pour être remarqué
Le gars au hasard

3
@Therandomguy cela nécessite quelque chose comme interface x{void f(Object...a);}pour être défini, et ce lambda doit être soit stocké dans une variable de ce type d'interface, soit être passé à une méthode qui attend ce type d'interface, donc je ne suis pas vraiment sûr que cela compte pour ce défi (même java lambdas sont généralement autorisés dans les challenges codegolf)
SamYonnou

3
@SamYonnou Il n'y a pas de différence avec les autres lambdas et, comme vous l'avez dit, les lambdas vont bien .
Olivier Grégoire

@ OlivierGrégoire Je sais que les lambdas sont autorisés. Mon point de vue était que, comparé à JavaScript par exemple, vous avez besoin de beaucoup de code supplémentaire pour le configurer, même si vous utilisez quelque chose comme REPL et évitez ainsi d'avoir recours à une classe / méthode principale ( la nécessité de définir l'interface est ce qui la distingue de JavaScript)
SamYonnou

@ OlivierGrégoire: Je connais un peu de Java mais je ne l'ai pas suivi du tout. J'ai été intéressé de voir le commentaire de Sam sur ce qui se passe de mieux en matière de blindage dans une réponse en Java qui laisse les choses vraiment courtes. Je conviens que cela devrait être autorisé (même si cela vous donne quelque chose que vous n’obtenez pas normalement avec les fonctions Java, d’accord, donc ce n’est pas seulement la réduction standard, elle vous donne le comptage des arguments intégré). En plus de cela, il est toujours intéressant de répondre à "Java battant JS".
Peter Cordes

25

JavaScript, 15 octets

[].push.bind(0)

La Array.prototype.pushfonction prend un nombre quelconque d'arguments, les ajoute à son tableau et retourne la taille du tableau. Par conséquent, la pushfonction utilisée sur un tableau vide renvoie le nombre d'arguments fournis à push.

f = [].push.bind(0)

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

f()
> 0

Le .bind(0)donne simplement à la pushfonction une thisvaleur fixe pour pouvoir la stocker dans une variable. En fait, l'identifiant à 7 octets [].pushpeut être utilisé littéralement (mais non attribué) sans bind:

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

[].push()
> 0

19

JavaScript (ES6), 16 octets

(...a)=>a.length


18

Haskell , 108 107 95 94 octets

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

Essayez-le en ligne!

C'était étonnamment difficile à faire fonctionner, mais je me suis amusé à essayer de trouver comment implémenter quelque chose de trivial dans des langages impératifs.


Bon sang, vous m'avez battu. fest optionnel si vous dites z 0est la fonction sans la liaison, main = print $ ((z 0) pi 0 () [] :: Int)fonctionne donc .
Angs

Et par là, je veux dire que les types fonctionnent lorsqu'ils sont utilisés comme une fonction anonyme, ce qui vous permet de tout supprimer des deux derniers rangs, saufz 0
Angs

Nice, merci! Il se trouve que je faisais quelque chose de mal quand je testais la fonction anonyme. J'ai essayé votre exemple et cela a bien fonctionné.
user9549915

Je suppose que cela ::Intdevrait être compté dans le nombre d'octets, car le type de réponse doit être déclaré tôt ou tard, comme dans main = print $ ((z 0 :: Double -> Integer -> () -> [a] -> (Int->Int->Int) -> IO () -> Int) pi 0 () [] (+) main). Je pense aussi que cela ne fonctionne que pendant le temps de compilation, donc quelque chose comme foldl(\a b->a b) (z 0) $ [1..5])::Intne peut pas fonctionner. Quoi qu'il en soit, c'est génial.
Angs

2
s/imperative/non-curry/
user202729


12

Zsh , 7 5 octets

<<<$#

Essayez-le en ligne!


Bien qu'il devrait probablement être emballé:f(){ echo $#; }
murou

8
@muru Cela me convient comme programme complet.
Neil

Bien, je vois maintenant que l'OP veut seulement une fonction ...
Neil

2
Les scripts shell @Neil agissent exactement comme des fonctions. OP n'indique pas ce qu'est une fonction, je prétends que ma soumission est simplement une fonction enregistrée sur le disque.
Pavel

9

Brain-Flak , 6 octets

Ma première solution Brain-Flak digne d'être publiée, je suppose que c'est le bon outil pour ce travail:

([]<>)

Essayez-le en ligne!

Explication

Lors de l'exécution d'un programme Brain-Flak, la pile de gauche contient initialement tous les arguments. À partir de là, c'est simplement une question 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 octets

Tr[1^{##}]&

Essayez-le en ligne!

Proposé par JungHwan Min. Certaines restrictions (l'entrée doit être rectangulaire) mais nous ne sommes pas obligés de gérer une entrée arbitraire.

11 octets

Length@!##&

Essayez-le en ligne!

Une autre solution de 11 octets suggérée par Martin Ender. Cela semble être une erreur s’il n’ya pas une entrée, mais renvoie toujours la valeur correcte dans tous les cas.

12 octets

Length@{##}&

Essayez-le en ligne!

Ma solution d'origine.

Dans Mathematica ##, un nombre variable d’arguments est défini dans une fonction. {et les }enroule dans une liste et Length@prend la longueur de cette liste. &à la fin, cela devient une fonction réelle.


7

R , 30 octets

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

Essayez-le en ligne!


1
function(...)nargs()est de 20 octets, mais l’utilisation length(...)était ma première approche jusqu’à googler une nargsfonction similaire.
Giuseppe

@ Giuseppe hmm, j'ai essayé de convertir le list(...)en logique pour sum()pouvoir l'utiliser, mais c'est délicat: /
JAD

1
Haha, n'essaie pas de m'attirer dans cet argument;)
RoryT

1
@RoryT oh, en fait, les documents R disent se combiner. Nevermind: D
JAD

2
...length() fait la même chose quelength(list(...))
Giuseppe

7

Bash, 12 octets (merci à paxdiablo pour la sauvegarde de 4)

n()(echo $#)

Copier et coller à une invite bash. Ensuite, lancez la fonction n à l’invite:

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

2
Bienvenue chez PPCG!
Martin Ender

pouvez-vous simplement dire que c'est un script "./n" et non une fonction? alors c'est juste echo $#:, 7 octets. (ce sera alors n'importe quel shell que vous utiliserez pour lancer le script "./n" avec. c'est-à-dire que vous exécuterez bash? alors quand vous: ./n arg1 ... argnil sera interprété par bash.)
Olivier Dulac

@Olivier Dulac Le défi dit clairement une fonction.
Wastrel

7

C ++ 14 (gcc) , 34 octets

En tant que fonction lambda variadique générique (C ++ 14 requis):

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

Essayez-le en ligne!

Réponse précédente (incorrecte): 32 octets

Il manquait le template<class...T>et(p)

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

6
C ++ 14, C ++ 11 n'a pas de lambda générique.
Quentin

1
Vous pouvez ajouter l'indicateur facultatif pour pouvoir supprimer les parenthèsesp (et -wdésactiver l'avertissement).
nwp

@ nwp: cela ne -fpermissivevous coûterait-il pas les 12 octets? Si ce n'est pas la norme ISO C ++ ou GNU C ++.
Peter Cordes

@PeterCordes C'est probablement le cas et il est prévu d'éviter toute solution triviale à 0 octet en passant le programme via la ligne de commande. Je n'ai simplement pas pensé à cela ici parce que cela ne semble pas être abusif.
nwp

@Quentin corrigé -> C ++ 14
Bierpfurz


5

Octave , 9 octets

@()nargin

Essayez-le en ligne!

Fonction anonyme en prenant un certain nombre d'arguments (et rejeter silencieusement le lot), et délivre en sortie le nombre d'arguments à travers le haut- nargin. Cela ne fonctionne pas dans MATLAB, où vous devez vararginautoriser de nombreux arguments arbitraires.



4

Perl 5 , 9 octets

sub{~~@_}

Essayez-le en ligne!


Une recherche rapide des réponses sur tout le site semble indiquer que vous pouvez laisser de côté lesub
ASCII uniquement

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


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.