1

I previously asked a question about C functions which take an unspecified number of parameters e.g. void foo() { /* code here */ } and which can be called with an unspecified number of arguments of unspecified type.

When I asked whether it is possible for a function like void foo() { /* code here */ } to get the parameters with which it was called e.g. foo(42, "random") somebody said that:

The only you can do is to use the calling conventions and knowledge of the architecture you are running at and get parameters directly from the stack. source

My question is:

If I have this function

void foo()
{
    // get the parameters here
};

And I call it: foo("dummy1", "dummy2") is it possible to get the 2 parameters inside the foo function directly from the stack?

If yes, how? Is it possible to have access to the full stack? For example if I call a function recursively, is it possible to have access to each function state somehow?

If not, what's the point with the functions with unspecified number of parameters? Is this a bug in the C programming language? In which cases would anyone want foo("dummy1", "dummy2") to compile and run fine for a function which header is void foo()?

Mat
  • 202,337
  • 40
  • 393
  • 406
Jacob Krieg
  • 2,834
  • 15
  • 68
  • 140
  • If you know how it was called (what are the types of the parameters), you can do that, in a machine-dependent way (but then why have such a function?) If you don't know, you cannot know. – n. m. could be an AI Mar 02 '14 at 11:20
  • Also note that (in modern C anyway) `void foo() {}` does not take any arguments. `void foo();` (declaration only, not joined with the definition) on the other hand doesn't say what `foo` takes. So in both your examples, you can't pass anything to foo. – Mat Mar 02 '14 at 11:22

3 Answers3

1

Yes you can access passed parameters directly via stack. But no, you can't use old-style function definition to create function with variable number and type of parameters. Following code shows how to access a param via stack pointer. It is totally platform dependent , so i have no clue if it going to work on your machine or not, but you can get the idea

long foo();

int main(void)
{
    printf( "%lu",foo(7));
}

long foo(x)
 long x;
{
    register void* sp asm("rsp");
    printf("rsp = %p rsp_ value = %lx\n",sp+8, *((long*)(sp + 8)));
    return *((long*)(sp + 8)) + 12;
}
  1. get stack head pointer (rsp register on my machine)
  2. add the offset of passed parameter to rsp => you get pointer to long x on stack
  3. dereference the pointer, add 12 (do whatever you need) and return the value.

The offset is the issue since it depends on compiler, OS, and who knows on what else. For this example i simple checked checked it in debugger, but if it really important for you i think you can come with some "general" for your machine solution.

Dabo
  • 2,371
  • 2
  • 18
  • 26
  • @Jason Swartz: Here is the original answer, i played with different options, if you don't define parameter in function definition nothing passed to function. When you try to pass more params then appears in function definition redundant params omitted. – Dabo Mar 02 '14 at 13:03
1

Lots of 'if's:

  1. You stick to one version of a compiler.
  2. One set of compiler options.
  3. Somehow manage to convince your compiler to never pass arguments in registers.
  4. Convince your compiler not to treat two calls f(5, "foo") and f(&i, 3.14) with different arguments to the same function as error. (This used to be a feature of, for example, the early DeSmet C compilers).

Then the activation record of a function is predictable (ie you look at the generated assembly and assume it will always be the same): the return address will be there somewhere and the saved bp (base pointer, if your architecture has one), and the sequence of the arguments will be the same. So how would you know what actual parameters were passed? You will have to encode them (their size, offset), presumably in the first argument, sort of what printf does.

Recursion (ie being in a recursive call makes no difference) each instance has its activation record (did I say you have to convince your compiler never optimise tail calls?), but in C, unlike in Pascal, you don't have a link backwards to the caller's activation record (ie local variables) since there are no nested function declarations. Getting access to the full stack ie all the activation records before the current instance is pretty tedious, error prone and mostly interest to writers of malicious code who would like to manipulate the return address.

So that's a lot of hassle and assumptions for essentially nothing.

user1666959
  • 1,805
  • 12
  • 11
0

If you declare void foo(), then you will get a compilation error for foo("dummy1", "dummy2").

You can declare a function that takes an unspecified number of arguments as follows (for example):

int func(char x,...);

As you can see, at least one argument must be specified. This is so that inside the function, you will be able to access all the arguments that follow the last specified argument.

Suppose you have the following call:

short y = 1000;
int sum = func(1,y,5000,"abc");

Here is how you can implement func and access each of the unspecified arguments:

int func(char x,...)
{
    short y = (short)((int*)&x+1)[0]; // y = 1000
    int   z = (int  )((int*)&x+2)[0]; // z = 5000
    char* s = (char*)((int*)&x+3)[0]; // s[0...2] = "abc"
    return x+y+z+s[0];                // 1+1000+5000+'a' = 6098
}

The problem here, as you can see, is that the type of each argument and the total number of arguments are unknown. So any call to func with an "inappropriate" list of arguments, may (and probably will) result in a runtime exception.

Hence, typically, the first argument is a string (const char*) which indicates the type of each of the following arguments, as well as the total number of arguments. In addition, there are standard macros for extracting the unspecified arguments - va_start and va_end.

For example, here is how you can implement a function similar in behavior to printf:

void log_printf(const char* data,...)
{
    static char str[256] = {0};
    va_list args;
    va_start(args,data);
    vsnprintf(str,sizeof(str),data,args);
    va_end(args);
    fprintf(global_fp,str);
    printf(str);
}

P.S.: the example above is not thread-safe, and is only given here as an example...

barak manos
  • 29,648
  • 10
  • 62
  • 114
  • Your example gives prototype, the OP asks for the case where function declaration given. Read the question and the answer he linked http://stackoverflow.com/a/22074190/2549281 – Dabo Mar 02 '14 at 11:30
  • With `gcc 4.8.1` I don't get an error when calling `foo("dummy1", "dummy2")`. I do know about variadic functions but this was not my question. I didn't know, however, about the approach you used for `int func(char x,...)`, I only knew about this example: https://www.gnu.org/software/libc/manual/html_node/Variadic-Example.html#Variadic-Example – Jacob Krieg Mar 02 '14 at 11:37
  • @Dabo why did you remove your answer? It was working and I was reading it more closely now?... – Jacob Krieg Mar 02 '14 at 11:40
  • @Jason Swartz: Not sure from your comment, whether this answer is helpful or not. Let me know if not, and I will remove it. – barak manos Mar 02 '14 at 11:43
  • @barakmanos I would like you to keep it. As Dabo said modern C doesn't allow this code but some compilers do compile it successfully e.g. `GCC`. – Jacob Krieg Mar 02 '14 at 11:45
  • @Jason Swartz: No problem, though, what does that have to do with C#? – barak manos Mar 02 '14 at 11:46
  • @JasonSwartz i removed the answer cause i realized that i do add prototype to the function. I undeleted it as you interested in it, but it won't work if you remove `long x;` from function definition. – Dabo Mar 02 '14 at 11:48