Draeton
Draeton

Reputation: 135

Is not passing all the arguments to a function bad?

I've been experimenting with 'dynamically calling functions' using the source code below. After successfully testing this code with testing_function only accepting the first two arguments, I added in a third and decided 'not to supply the argument' when I call the function. I've noticed that when I do this, the value of the third argument is not (necessarily) 0, but a 'random' value that which I do not know the origin of.

Questions follow:

Foreword to source code follows:

I am running using Linux, compiling/calling a linker with GCC 4.6.3, and receive no compilation/linking warnings/errors when utilizing this code. This code executes 'perfectly'. I call gcc like the following:

gcc -x c -ansi -o (output file) (input file, .c suffix)

Source code follows:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

/* Function for testing. */
int testing_function(char* something, char* somethingelse, int somethingadditional)
{
    int alt_errno = errno;
    if ((something != NULL)&&(somethingelse != NULL))
    {
        errno = 0;
        if (fprintf(stdout, "testing_function(\"%s\", \"%s\", %d);\n", something, somethingelse, somethingadditional) <= 0)
        {
            if (errno != 0)
            {
                int alt_alt_errno = errno;
                perror("fprintf(stdout, \"testing_function(\\\"%%s\\\", \\\"%%s\\\", %%d);\\n\", something, somethingelse, somethingadditional)");
                errno = alt_errno;
                return alt_alt_errno;
            }
            else
            {
                errno = ENOSYS;
                perror("fprintf(stdout, \"testing_function(\\\"%%s\\\", \\\"%%s\\\", %%d);\\n\", something, somethingelse, somethingadditional)");
                errno = alt_errno;
                return ENOSYS;
            }
        }
        else
        {
            errno = alt_errno;
            return 0;
        }
    }
    else
    {
        errno = ENOSYS;
        perror("testing_function(char* something, char* somethingelse, int somethingadditional)");
        errno = alt_errno;
        return ENOSYS;
    }
}

/* Main function. */
int main(int argc, char** argv)
{
    int (*function)(char*, char*);
    *(void**) (&function) = testing_function;
    exit(function("Hello", "world!"));
}

Upvotes: 6

Views: 2492

Answers (5)

R.. GitHub STOP HELPING ICE
R.. GitHub STOP HELPING ICE

Reputation: 215397

Calling a function with too few arguments is extremely dangerous. Under most ABIs, the stack slots for arguments are not call-preserved, meaning the compiler is free to generate for a function code which overwrites this portion of the stack. If the caller was not aware of the actual number of arguments the callee expects, and thus did not leave sufficient space for them, the callee will happily clobber the caller's local storage, possibly even including the return address.

On some architectures/ABIs with pass-by-register this does not apply until you exceed the number of arguments passed in registers, but on other pass-by-register systems (MIPS comes to mind), argument slots on the stack are reserved (and the callee is free to clobber them) even for arguments that are passed in registers.

In short, don't call functions with the wrong number or type of arguments. It's undefined for very good reasons.

Upvotes: 1

justin
justin

Reputation: 104698

Where are these values originating from?

The compiler sets up a call before it is made. When a function is entered, it knows how to locate its parameters and where to store its return values. Specifically, the compiler has a specification which allows it to say "Ok. Given the function signature, I can expect this parameter in this register" or if parameters are passed on the stack "by offsetting the current stack position by N bytes". This is based on the calling conventions specified by the architecture's ABI (Application Binary Interface). So the parameters may be stored in registers and/or on the stack, and a place for the return value is also reserved. The function also knows the current position on the stack.

So the function simply reads the parameters from where it expects them to exist. Generally, the parameters you have not passed are garbage values read from the registers or the stack, which were not written to prior to the call. Note that your function could not only read these values, but write them.

Additionaly, how are arguments passed to functions?

The compiler just writes them to the register or stack area specified by the ABI.

Is it bad practice to not pass arguments?

Yes. The exception to this is a va list (dangerous beasts in their own right): int foo(int a, ...);, where the function specifies its expectation using mechanisms such as sentinels and format specifiers.

Can one be prepared for additions to a function's arguments without recompiling code utilizing the function? (example: a dynamically loaded library's function gains an accepted argument but code utilizing the function isn't going to be recompiled).

The C function could be located dynamically and called (It would fail for C++, btw). Therefore, it is generally best to consider signatures of shipped APIs frozen when loaded dynamically or linked to a static image which is out of sync with the header visible to your translations.

Now, you can fake some of this and get it to work, but it is typically a bad idea because one minor slip and you can introduce undefined behavior.

Upvotes: 0

Zan Lynx
Zan Lynx

Reputation: 54345

Function parameters are passed depending on the C ABI used by the compiler. This can mean they are passed on the stack or in registers or in a combination of both. I believe that 32-bit Intel systems commonly pass in the stack while 64-bit Intel pass mostly in registers with the overflow going on the stack.

Where do the random values for unpassed arguments come from? They come from the register or stack position that should have held the value. The called function does not know that the argument wasn't passed so it pulls it anyway.

If all of the arguments are supposed to be on the stack this can lead to bad problems because the function will pull off more stack items than exist. In the worst case it will wipe out the function return address.

When using registers it isn't much of a problem except for the random value.

From the above information you should be able to gather that it isn't supported and you shouldn't do it and in general it won't work.

What will work is variable argument lists. For example, printf does it. So does the open() POSIX function. The open declaration looks like the following:

extern int open (__const char *__file, int __oflag, ...);

See the triple dot? That declares a variable argument list. It can contain 0 to any number of arguments. They are accessed using special functions. The only way to know how many arguments to expect is one of the previous arguments. In the case of open(), the oflag value. For printf() the format string.

Upvotes: 2

wallyk
wallyk

Reputation: 57784

In all computing environments, function arguments are collected and arranged in sequential memory somewhere—commonly on the CPU stack, but for some architectures it could be in a sequence of CPU registers—or a combination of registers and memory.

Only a few CPUs provide a mechanism for a called function to determine and verify the number of parameters passed to it. The VAX CPU is a major example.

Most architectures rely on the programmer doing the right thing: if a function is declared to accept three parameters, then wherever that function is called, there had better be (at least) three parameters. If there is not, the C standard says you will get "undefined behavior". In your specific case, whatever happens to have last been written to where the third parameter should have been placed is what you get. For gcc/Linux on an x86, it will be CPU stack memory.

Upvotes: 0

zwol
zwol

Reputation: 140748

Where are these values originating from?

Generally they will be memory or register garbage from previous operations.

Additionaly, how are arguments passed to functions?

It depends on the platform ABI; generally either in a designated set of registers or at fixed offsets from a "stack pointer".

Is it bad practice to not pass arguments?

Yes. It triggers "undefined behavior"; the compiler is entitled to crash your program the moment you do it, or worse.

Can one be prepared for additions to a function's arguments without recompiling code utilizing the function? (example: a dynamically loaded library's function gains an accepted argument but code utilizing the function isn't going to be recompiled).

No. Whenever you change the argument list of a C function that is part of a library ABI you must also change its name. (There are tricks you can pull to hide this in the source-level API, but they are all veneers over the fundamental tactic of changing the function's name.)

In C++ of course the changed argument list is a new overload, but that's implemented by the compiler changing the name for you.

Upvotes: 7

Related Questions