I have a function in C, which takes a bunch of arguments, and I would like to treat those arguments like an array and access them by number. For example, say I want to take 6 arguments plus a parameter from 1 to 6, and increment the corresponding argument. I could do:
void myFunc(int arg1,int arg2,int arg3,int arg4,int arg5,int arg6,n)
{
if (n==1) ++arg1;
else if (n==2) ++arg2;
else if (n==3) ++arg3;
else if (n==4) ++arg4;
else if (n==5) ++arg5;
else if (n==6) ++arg6;
}
But that's a bit messy. Is there a neater way to do this?
Although as suggested in the comments passing a pointer to an array may be easier. If you really want to go with arguments then your best bet may be to use a variadric function:
void myFunc(int n, ...)
{
va_list ap;
int arg;
va_start(ap, n);
while (--n)
arg = va_arg(ap, int); /* Increments ap to the next argument. */
va_end(ap);
arg++;
}
I'd do it like this:
void myFunc(int n, ...)
{
va_list args;
va_start(args, n);
int temp;
for(n; n; --n)
{
temp = va_arg(vl, int);
}
temp++;
va_end(args);
}
A few things to note:
This does no handling if n == 0, and will be wrong in that case.
Because C is pass by value, this will increment the variable locally, (as your original function), but the change will NOT take effect outside the function!
You can use a temporary array of pointers to your arguments, then you can access them through this array of pointers:
void myFunc(int arg1,int arg2,int arg3,int arg4,int arg5,int arg6,n)
{
int *array_of_args[] = {&arg1, &arg2, &arg3, &arg4, &arg5, &arg6};
if (n >= 1 && n <= 6)
++*array_of_args[n - 1];
}
This is not better than your original code, but if your code uses the array-access several times, this hack will make the code smaller.
Pass your arguments in as an array. Here I just used literals, but you could replace 1,2,3,4 with your own variables like arg1, arg2, and so on.
int myNumbers[] = { 1, 2, 3, 4 };
myFunc(myNumbers, sizeof myNumbers / sizeof myNumbers[0]);
Then, your function needs to be prepared to accept the array. Also, rather than using six if's to check six arguments, we can write a for loop. However, that is entirely unrelated to the question and I understand you may be doing this for a class assignment.
void myFunc(int *args, int numArgs)
{
int i = 0;
for(i; i < numArgs; i++)
{
if(args[i] == i+1) ++args[i];
}
}
Related
I looking to create a variadic function in C that allows to do something like this:
Send({1,2,3,4,5});
Send({4,5,2});
Send({1,1,1,1,1,1,1,1,1,1,1,1,1});
Note there is no length input and the array is placed inline and without any setup or creation of any variable
Currently i am using formal variadic option such as below (example from here), which is quite convenient but also prone to mistakes which are sometimes hard to debug such as forgetting to place the num_args (still compiles), placing the wrong number of elements etc.
int sum(int num_args, ...) {
int val = 0;
va_list ap;
int i;
va_start(ap, num_args);
for(i = 0; i < num_args; i++) {
val += va_arg(ap, int);
}
va_end(ap);
return val;
}
The typical way to define a function that operates on an arbitrary number of arguments of the same type is to use an array:
int sum(const int array[], size_t n)
{
int sum = 0;
while (n--) sum += array[n];
return sum;
}
That would mean that you would have to create an auxiliary array for each call and invoke the function, perhaps with a countof macro that uses sizeof to determine the size of the array and its first member.
As of C99, you can use compound literals to create such arrays:
int s = sum((int[]){1, 1, 2, 3, 5}, 5);
That might be more convenient and typesafe on the array elements, but still has the danger of getting the count wrong.
You can use a variadic macro to combine compound literals and the countof idiom:
#define SUM(...) sum((int[]){__VA_ARGS__}, \
sizeof((int[]){__VA_ARGS__}) / sizeof(int))
(The compound literal argument of the sizeof will only be evaluated for its size.)
Use it like this:
printf("%d\n", SUM(1, 2, 3, 4, 5));
printf("%d\n", SUM(200.0, 30.0, 5.0));
printf("%d\n", SUM());
(I'm not sure whether such a macro is useful, though. The sum example is contrived at best: sum(a, b, c) can be written as a + b + c. Perhaps a min or max macro for more than two arguments might be useful. In general, I find that I have the data I want in array form already when I work in C.)
If you don't want to pass length, you can use a sentinel value to mark the end. If you don't require full range of int, just use (for example) INT_MIN.
Send(1,1,1,1,1,1,1,1,1,1,1,1,1, INT_MIN);
And use that as end condition in your function's loop.
If you need full range of 32 bits, you could pass 64 bit integers so sentinel value can be outside the range of 32 bits. This will be a bit clunky though, you need to make all the parameters be 64 bit then, probably with suffix ll, and this will also make the code less portable/future proof.
One alternative is to use strings. Depending on your purpose, this can be quite robust, because compiler will warn about invalid format string. Here's the code I tested on gcc, and I belive it also works on clang. I hope it is sufficiently self-explanatory with the comments.
The __attribute__ ((format (printf, 1, 2))) part is quite important, it is the only thing making this robust, because it allows the compiler to check the arguments and produce warnings.
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// helper function to get a single string,
// just like vsprintf, except
// returns malloc buffer containing the result text
char *buf_print(const char *fmt, va_list ap) {
int bufsize = 0;
char * buf = NULL;
{
// get the exact size needed for the string,
// ap can only be processed once, a copy is needed
va_list ap2;
va_copy(ap2, ap);
bufsize = vsnprintf(NULL, 0, fmt, ap2);
va_end(ap2);
}
if (bufsize >= 0) {
buf = malloc(bufsize + 1);
if (buf) {
bufsize = vsnprintf(buf, bufsize + 1, fmt, ap);
if (bufsize < 0) {
free(buf);
buf = NULL;
}
}
}
return buf;
}
// get next parseable integer from a string,
// skipping any leading invalid characters,
// and returning pointer for next character, as well as number in *out,
// or returning NULL if nothing could be parsed before end of string
char *parse_next_ll(char *buf, long long *out) {
char *endp = NULL;
while (*buf) {
*out = strtoll(buf, &endp, 10);
if (endp != buf) return endp; // something was parsed successfully
++buf; // nothing was parsed, try again from the next char
};
return NULL; // means *out does not contain valid number
}
// Go through all integers in formatted string, ignoring invalid chars,
// returning -1 on error, otherwise number of valid integers found
int
__attribute__ ((format (printf, 1, 2)))
Send(const char *fmt, ...)
{
int count = -1;
va_list ap;
va_start(ap, fmt);
char * const buf = buf_print(fmt, ap); // needs to be freed, so value must not be lost, so const
va_end(ap);
if (buf) {
count = 0;
long long number = 0;
char *s = buf;
while (s && *s) {
s = parse_next_ll(s, &number);
if (s) {
++count;
printf("Number %d: %lld\n", count, number);
}
}
free(buf);
}
return count;
}
int main()
{
int r = Send("1,2,3,4,%i", 1);
printf("Result: %d\n", r);
return 0;
}
Ouptut of that code:
Number 1: 1
Number 2: 2
Number 3: 3
Number 4: 4
Number 5: 1
Result: 5
I'm currently taking an accelerated C introductory course, so most of my learning is coming from message boards and online resources. I'm having trouble getting my code to output, i think the issue is with my swap_array.
Hi all, I've deleted my original code (don't know if this is encouraged, but just for the sake of clarity). Thanks for the comments. It was a mess so I wanted to edit and post my updated code but I'm not sure how to format the swaparray function below the main. Basically, I want the function to be able to swap the values in list 1 and 2 when called to do so in the main.
#include<stdio.h>
void fillarray(int list[], int size);
void printarray(int list1[], int list2[]);
void swaparray(int list1[], int list2[], int size);
int main()
{
int a[5], b[5];
int size = 5;
fillarray(a, size);
fillarray(b, size);
printf("List1 and List2 before the swap\n");
printf("List1\t\t\t\List2\n");
printarray(a, b);
swaparray(a, b, size);
printf("List1 and List2 before the swap\n");
printf("List1\t\t\t\List2\n");
printarray(a, b, size);
return 0;
}
//////FUNCTIONS////////
void fillarray(int list[], int size)
{
printf("Please enter 5 values for the array\n");
for (int i = 0; i < 5; i++)
{
scanf_s("%d", &list[i]);
}
}
void printarray(int list1[], int list2[])
{
for (int i = 0; i < 5; i++)
{
printf("%d\t\t\t %d", list1[i], list2[i]);
}
}
void swaparray(int list1[], int list2[], int size)
{
for (int i = 0; i < 5; i++)
{
}
}
I will try to help step-by-step and explain how you can get this to compile and run, and try to help you understand what's going on.
On line 4, you are declaring the swap_array function which currently takes as input 3 int variables. However, you want to pass in arrays instead (so you can swap them) for the first two parameters using the [] notation (e.g. int array1[]). The same goes for the print_array function just below. You also need to do this in the definitions of those functions (on line 21 and 34).
Let's look at the main function now. You are currently creating array in the main function. Great. That's fine. However, straight after, you are calling swap_array with arguments array1 and array2. You have not declared and initialised these two variables in your main function, so your program has no idea what to do.
So what you need to do in main is create two arrays, array1 and array2 (similar to how you do it for array) and then feed those into the swap_array function.
Finally, you are calling print_array with arguments print1 and print2. Again, these variables are nowhere to be seen in your main. What exactly do you want to print? You want to print the two arrays you have just swapped: array1 and array2. Therefore these need to be the arguments that you feed into the print function, not print1 and print2.
I think you are getting confused between the difference between parameters and arguments. On lines 3, 4, 5, 21, 34 and 47, you have declared/defined functions. In these functions are the parameters. These parameters are used directly within the function as part of your calculations. Think of them as 'placeholders' for real values. When you call a function (e.g. in main), you can set these parameter values to whatever you want (e.g. value or the value of a variable), and these are called arguments (as you do in lines 15, 16 and 17).
I hope this helps. You should be able to compile and run with these changes.
Assuming that by swap you meant reverse, to accomplish your task I'd use stack, like so:
#include <stdio.h> /* stderr, fprintf, printf */
#include <stdlib.h> /* malloc, abort, free */
int * stack; /* stack array */
int main(void) { /* remember about void! */
int size, i; /* variable holding size and stack pointer, could name it better */
printf("Amount of elements:"); /* ask for amount of elements */
scanf("%d", &size); /* size now contains it */
stack = malloc(sizeof(int) * size); /* allocate enough memory */
if(!stack) { /* succeed? */
fprintf(stderr, "Out of memory."); /* no, print error */
abort(); /* bail out */
}
for(i = 0; i < size;) { /* now, read stack */
scanf("%d", &stack[i++]);
}
printf("Reversed:\n");
for(i = size; i > 0;) { /* and print it from the back */
printf("%d\n", stack[--i]);
}
free(stack);
}
Please remember to validate user input (I didn't include that; it may be your exercise).
When I am running this variadic function I am getting warning array subscript has type 'char', and then it crashes. I suspect the problem occurs somewhere in the while loop. I already tried many methods to prevent this function from crashing. For example: I tried changing (argp, char) to (argp, int) but this will not produce correct output . I also not allowed to change the prototype function. Please help!
char fancyMostFrequentChar(char c, ...) {
char j;
int max = 0;
int fre[255] = { 0 };
char temp;
va_list argp;
if (c == '\0')
return '\0';
va_start(argp, c);
//This for loop will run through the arguments
//and record their frequencies into fre[] array
while ((temp = va_arg(argp, char)) != '\0') {
++fre[temp];
if (fre[temp] > max) {
max = fre[temp];
j = temp;
}
}
va_end(argp);
return j;
}
You should definitely make temp an unsigned char or use a cast as you use it to index into the array fre. char can be, and often is, signed by default so any negative values would access outside the boundaries of the array. By the way, you should make fre one item larger to accommodate for all 8-bit values, including 255.
Note that you do not count the first argument, so fancyMostFrequentChar('a', 'b', 'a', '\0') would return 'b' instead of 'a'.
Note also (as commented by M.M) that the first argument should be defined as int for va_start to have defined behavior:
7.16.1.4 The va_start macro
Synopsis
#include <stdarg.h>
void va_start(va_list ap, parmN);
Description
The va_start macro shall be invoked before any access to the unnamed arguments.
The va_start macro initializes ap for subsequent use by the va_arg and va_end macros. [...]
The parameter parmN is the identifier of the rightmost parameter in the variable parameter list in the function definition (the one just before the , ...). If the parameter parmN is declared with the register storage class, with a function or array type, or with a type that is not compatible with the type that results after application of the default argument promotions, the behavior is undefined.
You mention you are not supposed to change the prototype... Unfortunately the prototype as posted char fancyMostFrequentChar(char c, ...) cannot be used to access the arguments past the first one.
Here is a corrected version:
char fancyMostFrequentChar(int c, ...) {
int fre[256] = { 0 };
char temp, res = (char)c;
if (res != '\0') {
va_list argp;
va_start(argp, c);
++fre[(unsigned char)res];
//This for loop will run through the arguments
//and record their frequencies into fre[] array
while ((temp = va_arg(argp, char)) != '\0') {
if (++fre[(unsigned char)temp] > fre[(unsigned char)res]) {
res = temp;
}
va_end(argp);
}
return res;
}
va_arg(argp, char) is undefined behaviour. The use of va_arg macro requires that the type be unchanged under the default argument promotions -- which means you can't use float, nor any integer type narrower than int. (Character types are integer types). Reference: C11 7.16.1.1/2
It is also undefined behaviour to have the second argument to va_start be of a type that changes under the default argument promotions. So it is actually impossible to correctly access the variadic arguments for a function declared as char fancyMostFrequentChar(char c, ...).
To fix this you must change the prototype of the function, for example:
char fancyMostFrequestChar(int c, ...)
and the relevant part of your loop should be:
int temp;
while ((temp = va_arg(argp, int)) != '\0')
{
unsigned char index = (unsigned char)temp;
and then use index as your array index. It is then guaranteed to be in the range of 1 through to UCHAR_MAX (which is usually 255). For pedantic correctness you could change your array definition to be int fre[UCHAR_MAX+1].
Your code has a lot of problems, I hope I will catch all.
unsigned char fancyMostFrequentChar(unsigned char c, ...) // resolve ambigity of char
{
unsigned char j = '\0'; // was not initialized
int max = 0;
int fre[256] = {0}; // max unsigned char is 255 -> array length = 256 (0..255)
unsigned char temp;
va_list argp;
if (c == '\0')
return '\0';
va_start(argp, c);
//This for loop will run through the arguments
//and record their frequencies into fre[] array
while ((temp = va_arg(argp, unsigned char)) != '\0')
{
++fre[temp];
if (fre[temp] > max)
{
max = fre[temp];
j = temp;
}
}
va_end(argp);
return j;
}
After reading this and this, I understand that
void va_start(va_list ap, last);
Saves in va_list pa some information about arguments whose number and types are not known to the called function. Later arguments can be extracted via va_arg().
Documentation doesn't specify how things are put in va_list pa
In short: I need to know how its being saved in memory. Its memory representation.
#include<stdarg.h>
#include<stdio.h>
int sum(int, ...);
int main(void)
{
printf("Sum of 10, 20 and 30 = %d\n", sum(3, 10, 20, 30) );
return 0;
}
int sum(int num_args, ...)
{
int val = 0;
va_list ap;
int i;
va_start(ap, num_args);
for(i = 0; i < num_args; i++)
{
val += va_arg(ap, int);
}
va_end(ap);
return val;
}
What information is stored in pa ? how does it look in memory for the above example ?
And how from the above example it knows the type is int and not something else ?
It is implementation-dependent. Sometimes, va_list is simply char*. Basically, ap represents an array of pointers to chars. When calling va_arg(ap, TYPE) it reads memory from address at which ap points, but it reads it as TYPE type. So, if you call it with int it will read 4 bytes and convert it to int type.
I'm trying to make a function that takes a string with format (like printf, but instead of "%i" I want it to be "n" (for learning purposes, don't ask me why)). Here is the function:
void test(char* args, ...)
{
int length = strlen(args);
va_list list;
va_start(list, length);
for (int i = 0; i < length; i++)
{
if (args[i] == 'n')
{
printf("%i", va_arg(list, int));
}
}
}
The problem is that when I call it like this: test("n", 13); it gives another number (-858993460). What's the problem and how can I fix it?
You should call va_start like this:
va_start(list, args);
The second parameter of va_start must be the name of the last parameter of test before the ellipsis, which is args.