Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access to variadic function' arguments without va_list in C

Is it possible to iterate through variadic function' arguments using pointer (void pointer?) to last named argument? (I know that's not the right way to work with variadic arguments, but I'm still interested if that would work)

Setting pointer to the end of the string doesn't work, because after I start moving the pointer, it points to other strings used in the program.

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

void form_date(MON* datePtr, int dayMonth, int dayYear, int month);
MON* evaluate_date(MON* datePtr, int count, int dayArg);
void what_month(char *format, ...);
void output(MON* datePtr, int count);

int main(void)
{ 
what_month("ii", 126, 125);
return 0;
}

void what_month(char *format, ...){

    char* arg_ptr = format+2;
    int* arg_int_ptr;
    double* arg_double_ptr;

    MON dateArr[MAX_DATE];
    int count = 0;
    int dayYear;
    char *ptrFormat = format;

    for(; *ptrFormat != '\0'; ptrFormat++){
        if(*ptrFormat == 'i'){
            arg_int_ptr = (int*) arg_ptr;
            dayYear = *arg_int_ptr;
            arg_int_ptr++;
        }

        if(*ptrFormat == 'd'){
            arg_double_ptr = (double*) arg_ptr;
            dayYear = *arg_double_ptr;
            arg_int_ptr++;
        }
        evaluate_date(dateArr, count, dayYear);
            count++;
     }
    output(dateArr, count);
}


void form_date(MON* datePtr, int dayYear, int dayMonth, int month){
    char month_names[][15] = {"January", "February", "March", "April", "May", "June",
                              "July", "August", "September", "October", "November",
                              "December", "INVALID_MONTH"};

    datePtr->day_of_year = dayYear;
    datePtr->day_of_month = dayMonth;

    if(month == -1){
        strcpy(datePtr->month, month_names[12]);
    }
    else {
        strcpy(datePtr->month, month_names[month]);
    }
}

MON* evaluate_date(MON* dateArr, int count, int dayArg){
    int months_days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int j;
    int dayMonth;
    int sumDays = 0;


        if (dayArg > 365 || dayArg < 1){
            form_date(dateArr + count, dayArg, -1,  -1); 
            count++;
        }

        else {
            for(j = 0; j < 12; j++){
                sumDays += months_days[j];
                if (dayArg <= sumDays)
                    break;
            }
            dayMonth = months_days[j] - (sumDays - dayArg);
            sumDays = 0;
            if (dayMonth == 0){
                dayMonth++;
            }

            form_date(dateArr + count, dayArg, dayMonth, j);
        }
        return dateArr;
}

void output(MON* dateArr, int count){
    int i, j;

    for(i = 0; i < 80; i++)
        printf("_");

    printf("\n");
    for(i = 0; i < 80; i++)
        printf("_");

    for(j = 0; j < count; j++){
        if (j % 100 == 0 && j != 0){
            puts("Press any key to continue");
            getchar();
        }

        printf("\n%-7d  :::  %7d, %-8s %5s\n", dateArr[j].day_of_year, dateArr[j].day_of_month,
               dateArr[j].month, "|");
    }
    for(i = 0; i < 80; i++)
        printf("_");
}
like image 299
nmeln Avatar asked Nov 06 '13 13:11

nmeln


2 Answers

No, you cannot do that portably. To use variable function arguments you must use the facilities provided in <stdarg.h>.

You can, however, look at your platform's code generation and/or machine code output and get an understanding of the machine layout of the variable arguments, and perhaps use that for your own, platform-specific purposes.

like image 192
Kerrek SB Avatar answered Sep 19 '22 13:09

Kerrek SB


First - strings are not passed via value in C/C++ unless encapsulated in some struct/class so on the stack you'll find a pointer to a string not a string itself.

You should not handle variable argument list manually using pointers as that is not portable in first place.

Why not portable ? Here are some problems:

  • do not assume your code will be executed on x86 where stack pointer when push is executed behaves like *--sp = value
  • not all stacks (not on all archs) grow down and before storing value - ARM processor let's you implement stack push as *--sp = val, *++sp = val, --*sp = val, ++*sp = val (it is up to you)
  • what is 32bit int on one architecture might be 16bit int on other (or 64)
  • even with the same compiler - if you compile to 64bit instruction set you get different calling convension that spoils your code

Moreover in standard calling convention for C/C++ your last argument is pushed on stack first so you cannot use it for variadic functions (as you cannot immediately locate it fromyour func). cdecl calling convension pushes arguments on stack in reversed order ie.:

func(a,b)

is

push b
push a
call func

This reversed convention is only for the purpose of allowing variadic functions to work since your first argument a (which is always needed in varargs) can always be easily accessed from stack as it's location is known (because pushed last). To get other arguments (or to understand how many they are, or their types) you usually must parse first argument - just like in printf.

Hope this sheds some ligh on that.

Some additional info that may help:

  • assembler internals of calling a function
  • x86 calling conventions
  • x64 calling conventions
like image 40
Artur Avatar answered Sep 20 '22 13:09

Artur