How to tell if an optional argument was passed to a function C

1.3k Views Asked by At

Edit 3: For the code itself all together check the first answer or the end of this post.

As stated in the title I'm trying to find a way to tell if an optional argument was passed to a function or not. What I'm trying to do is something like how almost all dynamic languages handle their substring function. Below is mine currently, but it doesn't work since I don't know how to tell if/when the thing is used.

char *substring(char *string,unsigned int start, ...){
    va_list args;
    int unsigned i=0;
    long end=-1;
    long long length=strlen(string);
    va_start(args,start);
    end=va_arg(args,int);
    va_end(args);
    if(end==-1){
        end=length;
    }
    char *to_string=malloc(end);
    strncpy(to_string,string+start,end);
    return to_string;
}

Basically I want to still be able to not include the length of the string I want back and just have it go to the end of the string. But I cannot seem to find a way to do this. Since there's also no way to know the number of arguments passed in C, that took away my first thought of this.

Edit: new way of doing it here's the current code.

#define substring(...) P99_CALL_DEFARG(substring, 3, __VA_ARGS__)
#define substring_defarg_2 (0)
char *substring(char *string,unsigned int start, int end){
    int unsigned i=0;
    int num=0;
    long long length=strlen(string);
    if(end==0){
        end=length;
    }
    char *to_string=malloc(length);
    strncpy(to_string,string+start,end);
    return to_string;
}

and then in a file I call test.c to see if it works.

#include "functions.c"
int main(void){
    printf("str:%s",substring("hello world",3,2));
    printf("\nstr2:%s\n",substring("hello world",3));
return 0;
}

functions.c has an include for functions.h which includes everything that is ever needed. Here's the clang output(since clang seems to usually give a bit more detail.

In file included from ./p99/p99.h:1307:
./p99/p99_generic.h:68:16: warning: '__error__' attribute ignored
__attribute__((__error__("Invalid choice in type generic expression")))
               ^
test.c:4:26: error: called object type 'int' is not a function or function
      pointer
    printf("\nstr2:%s\n",substring("hello world",3));
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from test.c:1:
In file included from ./functions.c:34:
In file included from ./functions.h:50:
./string.c:77:24: note: instantiated from:
#define substring(...) P99_CALL_DEFARG(substring, 3, __VA_ARGS__)

GCC just says the object is not a function

Edit 2: Note that setting it to -1 doesn't change it either, it still throws the same thing. The compile options I'm using are as follows.

gcc -std=c99 -c test.c -o test -lm -Wall

Clang is the same thing(whether or not it works with it is another question.

ANSWER HERE

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include "p99/p99.h"
#define substring(...) P99_CALL_DEFARG(substring, 3, __VA_ARGS__)
#define substring_defarg_2() (-1)
char *substring(char *string, size_t start, size_t len) {
  size_t length = strlen(string);
  if(len == SIZE_MAX){
    len = length - start;
  }
  char *to_string = malloc(len + 1);
  memcpy(to_string, string+start, len);
  to_string[len] = '\0';
  return to_string;
}

You will need p99 from there. It is by the selected answer. Just drop into your source directory and you should be OK. Also to summarize his answer on the license. You're able to use it however you want, but you cannot fork it basically. So for this purpose you're free to use it and the string function in any project whether proprietary or open source.

The only thing I ask is that you at least give a link back to this thread so that others who happen upon it can learn of stack overflow, as that's how I do my comments for things I've gotten help with on here.

4

There are 4 best solutions below

16
On BEST ANSWER

Other than common belief functions with optional arguments can be implemented in C, but va_arg functions are not the right tool for such a thing. It can be implemented through va_arg macros, since there are ways to capture the number of arguments that a function receives. The whole thing is a bit tedious to explain and to implement, but you can use P99 for immediate use.

You'd have to change your function signature to something like

char *substring(char *string, unsigned int start, int end);

and invent a special code for end if it is omitted at the call side, say -1. Then with P99 you can do

#include "p99.h"

#define substring(...) P99_CALL_DEFARG(substring, 3, __VA_ARGS__)
#define substring_defarg_2() (-1)

where you see that you declare a macro that "overloads" your function (yes this is possible, common C library implementations use this all the time) and provide the replacement with the knowledge about the number of arguments your function receives (3 in this case). For each argument for which you want to have a default value you'd then declare the second type of macro with the _defarg_N suffix, N starting at 0.

The declaration of such macros is not very pretty, but tells at least as much what is going on as the interface of a va_arg function would. The gain is on the caller ("user") side. There you now can do things like

substring("Hello", 2); 
substring("Holla", 2, 2);

to your liking.

(You'd need a compiler that implements C99 for all of this.)


Edit: You can even go further than that if you don't want to implement that convention for end but want to have two distinct functions, instead. You'd implement the two functions:

char *substring2(char *string, unsigned int start);
char *substring3(char *string, unsigned int start, unsigned int end);

and then define the macro as

#define substring(...)                \
 P99_IF_LT(P99_NARG(__VA_ARGS__, 3))  \
 (substring2(__VA_ARGS__))            \
 (substring3(__VA_ARGS__))

this would then ensure that the preprocessor chooses the appropriate function call by looking at the number of arguments it receives.


Edit2: Here a better suited version of a substring function:

  • use the types that are semantically correct for length and stuff like that
  • the third parameter seems to be a length for you and not the end of the string, name it accordingly
  • strncpy is almost never the correct function to chose, there are situations where it doesn't write the terminating '\0' character. When you know the size of a string use memcpy.

char *substring(char *string, size_t start, size_t len) {
  size_t length = strlen(string);
  if(len == SIZE_MAX){
    len = length - start;
  }
  char *to_string = malloc(len + 1);
  memcpy(to_string, string+start, len);
  to_string[len] = '\0';
  return to_string;
}
0
On

In standard C, when using variable argument prototypes (...), there is no way to tell directly how many arguments are being passed.

Behind the scenes, functions like printf() etc assume the number of arguments based on the format string.

Other functions that take, say, a variable number of pointers, expect the list to be terminated with a NULL.

Consider using one of these techniques.

3
On

Unfortunately, you cannot use va_arg like that:

Notice also that va_arg does not determine either whether the retrieved argument is the last argument passed to the function (or even if it is an element past the end of that list). The function should be designed in such a way that the amount of parameters can be inferred in some way by the values of either the named parameters or the additional arguments already read.

A common "workaround" is to give the other "overload" a nice mnemonic name, such as right_substr. It will not look as fancy, but it will certainly run faster.

If duplicating implementation is your concern, you could implement left_substr, substring, and right_substr as wrappers to a hidden function that takes start and length as signed integers, and interprets negative numbers as missing parameters. It is probably not a good idea to use this "convention" in your public interface, but it would probably work fine in a private implementation.

5
On

In C, there's no such thing as an optional argument. The common idiom for situations like this is to either have two functions; substr(char *, size_t start, size_t end) and substr_f(char *, size_t start) or to have a single function where end, if given a special value, will take on a special meaning (such as in this case, possibly any number smaller than start, or simply 0).

When using varargs, you need to either use a sentinel value (such as NULL) at the end of the argument list, or pass in as an earlier argument the argc (argument count).

C has a very low amount of runtime introspection, which is a feature, not a bug.

Edit: On a related note, the correct type to use for string lengths and offsets in C is size_t. It is the only integer type that is guaranteed to be both large enough to address any character in any string, and guaranteed to be small enough to not be wasting space if stored.

Note too that it is unsigned.