3

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.

Morwenn
  • 21,684
  • 12
  • 93
  • 152
133794m3r
  • 5,028
  • 3
  • 24
  • 37
  • 6
    Sorry, but you can't. You have to pass something *to* the function to tell it how many arguments to look for (e.g., `printf` uses the format string). – Jerry Coffin Apr 22 '12 at 10:49
  • Completely off-topic: even if you were talking about real optional arguments, like in C++, you still couldn't tell in the function if an argument was omitted from the call, or if it was given but with the default value. – Mr Lister Apr 22 '12 at 11:09
  • @JerryCoffin, sorry but you can ... in some way, please see my answer. The preprocessor is much more powerful than most people are aware of, you can for example have it choose between two different function calls according to whether it receives two or three arguments. – Jens Gustedt Apr 22 '12 at 12:25
  • @JensGustedt: I've looked at it -- while it may be a reasonable alternative, it's not what he really asked for, nor does it contradict what I said in my comment. – Jerry Coffin Apr 22 '12 at 15:36
  • @Jerry. The question, in the title, is about optional arguments for functions, and the way you can do this in C is to use the preprocessor. – Jens Gustedt Apr 22 '12 at 15:55
  • @JensGustedt: That's not what you've shown. What you've shown in passing an optional argument to a macro, which can either *always* pass an argument to the function, or else call different functions based on the number of arguments. Neither lets the function determine whether an optional argument was passed or not though. In the first, the user could explicitly pass the value chosen to indicate that no value was passed, and in the second either function could be invoked directly. As I said, it may be a reasonable alternative, but neither is what he actually asked for. – Jerry Coffin Apr 22 '12 at 16:11
  • @Jerry, that is why I said "in some way". And it seems to me that macros are the way such things are foreseen by the modern versions of the standard. Type generic macros, e.g, go the same line. I think of "functional macro" as being a function interface. – Jens Gustedt Apr 22 '12 at 16:22

4 Answers4

6

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.

Williham Totland
  • 28,471
  • 6
  • 52
  • 68
  • Counting arguments of a function is not really a runtime introspection, since it is known at compile time at the calling side. – Jens Gustedt Apr 22 '12 at 11:56
  • @JensGustedt: Disregard that comment, I misread your comment. It is a feature often tied in with various aspects of runtime introspection, although it might not itself technically be introspection in and of itself. Irrespectively, attaching that kind of meta-data to a function call would turn it into a message dispatch, which is a feature more apt for a language with a runtime, rather than simple compiled code. – Williham Totland Apr 22 '12 at 12:09
  • I didn't see your first comment, so it is easy for me to disregard it :) Please look at my answer why I think that this feature of C (function calls have a known number of arguments at the call side) is an important one and how this can be used to actually implement what the OP had in mind. – Jens Gustedt Apr 22 '12 at 12:21
  • Ah... OK, well I'm going to go change it to size_t, I did this rather quickly to try to get it all working. I honestly don't remember why I was even writing this at the moment... but I know I wanted to have it in my "c library" folder. – 133794m3r Apr 23 '12 at 11:12
  • It might be useful for substring to take negative values, meaning offsets relative to the end of the string, so maybe `ssize_t` is appropriate. Of course, it alters the amount of error checking available. – Jonathan Leffler Apr 23 '12 at 18:53
4

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;
}
Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • Well, that's all kinds of ugly. ;) It's also the level of macro usage that you're not really writing C anymore. Still, it's a neat trick. – Williham Totland Apr 22 '12 at 12:29
  • @WillihamTotland, the preprocessor is part of the language. – Jens Gustedt Apr 22 '12 at 12:32
  • I'm not entirely sure I agree with you on that point, `cpp` is more of a compiler tool, IMO, considering that `cc` would never understand the unprocessed code. – Williham Totland Apr 22 '12 at 12:39
  • @WillihamTotland, the "preprocessing" phase is just one (of several) translation phase that is described in the C standard. It is proper to that standard (even C++ has slightly different rules) and C would not work without it. Also modern compilers don't implement the preprocessing in a different executable, its all integrated. – Jens Gustedt Apr 22 '12 at 14:00
  • I suppose. That doesn't mean it's impossible to abuse it. ;) – Williham Totland Apr 22 '12 at 14:46
  • :) There is not much in C, that you can't "abuse", `void*`, `va_arg`, casting ... and the preprocessor, yes. – Jens Gustedt Apr 22 '12 at 14:58
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/10366/discussion-between-williham-totland-and-jens-gustedt) – Williham Totland Apr 22 '12 at 14:58
  • I'll definitely mark this as the accepted answer if you include which headers I need to include in my includes.h file. – 133794m3r Apr 23 '12 at 11:10
  • @133794m3r, wouldn't just `#include "p99.h"` be satisfactory for you? – Jens Gustedt Apr 23 '12 at 13:21
  • @JensGustedt Well I didn't know if I just had to include that one header, or if I had to include various other headers inside of the file. I just wanted to make sure before I marked it as answered and then updated my post witht eh solution at thebottom. – 133794m3r Apr 23 '12 at 13:33
  • Also since it's using the QPL I don't know how well I trust this thing. From what I've read in the license, I cannot release any program that utilizes it under anything but the QPL. I also have to release full source code to all things that utilze any of p99. I normally try to do LGPLv3 for my projects that I release but this is saying that's impossible let alone any other license. Unless I've misread the thing. I'm hoping that I did since it's not DFSQ certified yet it reads like copyleft and forcing their license. I'm probably just not understanding the license right onmy first encounter. – 133794m3r Apr 23 '12 at 13:52
  • QPL is not my personal choice either, but a compromise with my employer. But it is not as bad as you understood it :) The idea of QPL is that nobody can fork it, and nobody can just integrate it in their source. There is no problem whatsoever in just *using* it and having any licence of your liking on your own code. LGPL would have been my favorite, too, but P99 has a subtlety that is not foreseen as such with LPGL: P99 is no library in the classical sense since it is only preprocessor *source* files. – Jens Gustedt Apr 23 '12 at 15:36
  • Ah ok, good. Thanks for clearing that up. Also on an unrelated note, for literally like 5 minutes I sat there wondering why you had Master Chief with a cowboy hat as your avatar. When I clicked it, it turned out as something different. Also my question as to the headers, was because I thought that you were some random person giving advice. I didn't know you were the person who wrote it. Anyway, thanks for the answer, and next time... I'll probably look at the docs before checking google since I think it'll have more tricks up its sleeve. – 133794m3r Apr 23 '12 at 17:42
  • OK, I just tried it, and now it's saying that the object is not a function. When I do it with 3 as it expects it works just fine. When I try it with 2 it doesn't. I'm going to edit my post above to add this. – 133794m3r Apr 23 '12 at 18:27
  • @133794m3r, sorry this happened because I typed the example without testing it. `substring_defarg_2` has to be a functionlike macro. The `()` were missing in my example. – Jens Gustedt Apr 23 '12 at 18:42
  • I should've realized that when I was looking at the code in your examples. Anyways thanks for the help here, and for the awesome way to work around one of the little things that bothered me about c. – 133794m3r Apr 23 '12 at 19:11
2

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.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • `size_t` should always be used for strings, and it being unsigned, -1 makes a poor special value. – Williham Totland Apr 22 '12 at 11:09
  • @WillihamTotland As far as the public-facing API goes, you are absolutely right: `size_t` is the right choice. I was talking about private function that *implements* three APIs and takes "poor man's version" of optional parameters: the rules are a lot more relaxed there, because you can change it without anyone noticing. – Sergey Kalinichenko Apr 22 '12 at 11:21
  • Yes and no: There's a *reason* for using `size_t`; that I've outlined in the Edit to my answer; but long story short: Using any other type has a definite risk of ending badly in very subtle ways. – Williham Totland Apr 22 '12 at 11:25
1

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.

Michael Slade
  • 13,802
  • 2
  • 39
  • 44