Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is not passing all the arguments to a function bad?

Tags:

c

function

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:

  • Where are these values originating from?
  • Additionaly, how are arguments passed to functions?
  • Is it bad practice to not pass arguments?
  • 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).

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!"));
}
like image 295
Draeton Avatar asked May 14 '13 21:05

Draeton


2 Answers

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.

like image 143
zwol Avatar answered Sep 18 '22 02:09

zwol


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.

like image 41
Zan Lynx Avatar answered Sep 22 '22 02:09

Zan Lynx