2

I'm working on something in c that requires a variadic function, and I need to do something depending on the type of each argument. I know via here that there's no way to check type during execution (and I'm not interested in the way they tried to solve it there), so I'm wondering if there's any sort of panic/recovery techniques in c, so as to implement a sort of try/catch (I've seen this article on pseudo try catches, but they won't allow for running non-executable code).

My thoughts are too loop through the variables and try different behaviors on them that only work with specific types. I know this might not be possible. Thanks in advance if you know of anything

Edit: If you are going to suggest that I should just refactor my code to not have to do that, let me explain: I'm trying to implement a println() function, similar to those in python and go, where you can simply enter all the variables you want to be printed and not have to worry about a format string (println(myvar, 10, "nice")). The obvious answer is just don't do it, since format strings really aren't that hard. But this sounds fun to me

Mauricio
  • 419
  • 4
  • 14
  • 1
    Post example code? – anatolyg Jul 20 '17 at 15:47
  • Look up "software interrupts". – Cpp plus 1 Jul 20 '17 at 15:48
  • 2
    I think you are using the wrong language. And also, you have [The XY Problem](http://xyproblem.info). – Iharob Al Asimi Jul 20 '17 at 15:48
  • surely if you call your function saying you're passing it something of type X and then pass it something that isn't of type X, then your code is wrong and you should fix it rather than have your function try and guess what you've actually passed to it? – Chris Turner Jul 20 '17 at 15:49
  • 1
    @IharobAlAsimi The C standard library provides a way to handle software interrupts – Cpp plus 1 Jul 20 '17 at 15:49
  • What are software interrupts? And even though they were called software interrupts (*I think they are called signals*), that's a terrible design. You are telling me that making the program crash intentionally just to infer the type of something is a good idea? – Iharob Al Asimi Jul 20 '17 at 15:51
  • Possible duplicate of [Best practices for recovering from a segmentation fault](https://stackoverflow.com/questions/8401689/best-practices-for-recovering-from-a-segmentation-fault) – anatolyg Jul 20 '17 at 15:51
  • In c you can't reliably cause a crash. So this is a terrible idea. – Iharob Al Asimi Jul 20 '17 at 15:52
  • @IharobAlAsimi That is how C++ exceptions are implemented. – Cpp plus 1 Jul 20 '17 at 15:52
  • @Cppplus1 1. That's not what exceptions are for. 2. This is not c++. That's one reason why c++ programmers are considered inferior. How on earth would you think that a robust design relies on undefined behavior? – Iharob Al Asimi Jul 20 '17 at 15:52
  • 1
    you can raise a signal, catch it in a handler and then use long jumps to go somewhere from there. – Serge Jul 20 '17 at 15:52
  • @IharobAlAsimi but the person who asked the question is asking how to implement try/catch-like behavior in C. – Cpp plus 1 Jul 20 '17 at 15:53
  • Because they have [The XY problem](http://xyproblem.info/). Trying to use a solution for c++ or other languages with `try`/`catch` constructs in c. – Iharob Al Asimi Jul 20 '17 at 15:54
  • This is now, an interesting question. – Iharob Al Asimi Jul 20 '17 at 15:55
  • @Serge think you could explain? – Mauricio Jul 20 '17 at 15:59
  • @IharobAlAsimi Yeah I noticed that I should've said why I want this solved, it's in the edit – Mauricio Jul 20 '17 at 16:00
  • @anatolyg That's not a duplicate, the top answer literally just says to refactor your code. That's not what I'm looking for. Also that's in c++. I'm working in c – Mauricio Jul 20 '17 at 16:02
  • @Mauricio You don't have to reject that question because one (accepted) answer doesn't suit. Look at other answers too! I think at least two answers there are usable, and may provide 90% of code you need. – anatolyg Jul 20 '17 at 16:06
  • @Mauricio -- a bunch of faults in c program raise system signals (i.e. segmentation fault, signal 11) which could be caught by a handler in your program. You can raise your own signals as well. You need to write a handler which will be registered to catch the signal. Long jumps allow you to set up jump targets in a program and then execute the jump (longjmp()). They covered in 'setjmp.h' and 'signal.h'. – Serge Jul 20 '17 at 16:07
  • 1
    @Serge Accessing a variable using the wrong type will not raise a signal in most cases. – interjay Jul 20 '17 at 16:14
  • @interjay -- i guess that the original question assumed that it would (crash and recovery). – Serge Jul 20 '17 at 16:20
  • if it crashed, you'd be entering undefined behaviour territory if you tried to continue on as if nothing bad had happened - what if the crash was caused by memory corruption? – Chris Turner Jul 20 '17 at 16:23
  • In general, as it mentioned in multiple places here, there is no way to check for the types at this level at all. Neither can you reliably check for the number of arguments nor their sizes. In particular when the calling convention partially uses registers and partially stack. You need to pass some info along the way to such a function. There could be partial solutions for specific platforms and calling conventions, requiring analyzing machine codes at assembler level. – Serge Jul 20 '17 at 16:25
  • @ChrisTurner -- right. do not **write** based on such args. But you can test for reading or ranges. – Serge Jul 20 '17 at 16:29

4 Answers4

3

Traditionally, variadic functions are written such that the type of each argument can be predicted by the contents of previous arguments.

You cannot reliably determine the type of an argument through trial and error. The caller places the arguments on the stack and it is up to the function to know the offset and type of each argument on the stack.

If for example you were to have the possibility of two 32 bit integers or one 64 bit integer.. or one signed 64 bit integer vs one unsigned 64 bit integer, how would you know the difference? The processor will happily treat an unsigned int as a signed int and compute the wrong result if you ask it to.. there will be no error telling you that you had the wrong type.

In your edited question, I now see what you are trying to accomplish. This kind of thing is easy to do in higher level languages like python, where all the arguments are objects which can be introspected if necessary to determine their type. C is not like that. You are working with the data at a much lower level.. a 64 bit integer is literally 64 bits of memory, containing an integer value. You have no metadata telling you the type. Your code has to know what type of data it is dealing with and where it is located. When arguments are passed on the stack, you have no way of knowing even know how many were passed or where one ends and the next starts, much less the type of each. Your function has to know all that. When you are dealing with normal (non-variadic) functions, this is something that the compiler takes care of for you, so you don't have to think about it much. Contrast with python where arguments are in a high level list or dictionary which gives you a lot of information about what it contains.

little_birdie
  • 5,600
  • 3
  • 23
  • 28
2

What you're talking about is declaring a function such as this...

void println(...);

since that is the only way a C compiler would be able to accept a function call with an arbitrary number of arguments of unknown type. But that won't compile as the ... has to be preceded by a named argument.

That named argument is typically used to define the format of the following arguments because as far as your compiled code is concerned, it's all just a load of bytes in memory.

Chris Turner
  • 8,082
  • 1
  • 14
  • 18
1

There is a fundamental difference between the languages you mentioned and .

For example uses a parser that will determine the type of each variable from it's input that is a text strings. So there is no problem, because you can attempt multiple conversions giving priority to the less likely and until you reach the text representation, because everything that can be printed can be represented as text, which in the end is the purpose of a print() function.

If were to infer the types at compile time for example then this would be possible. But doesn't have such a feature, types are expressed before compilation and can't be changed. In a variadic function it's impossible to tell what type each argument is, the function should have a method to determine this or all the arguments can be of the same type, for example text.

If all the arguments are of the same type, you can write a function that would accept the following syntax

println("example text and a ", "10", " which is a number");

but doing it like you can in or , is simple not possible at all, or at least not without a format string and variadic functions.

Also your attempt to try something and if it doesn't work try the next thing will not work because in you can cast a variable from many types to other incompatible type without something special happening, so for example suppose that you pass 10 and "nice" like you did in your example, then trying to extract "nice" as an integer will work and you will end up printing "nice"'s address (or probably not) instead of the string "nice".

I said probably not, because such thing in c is undefined behavior and the resulting behavior cannot really be predicted from the code alone.

Iharob Al Asimi
  • 52,653
  • 6
  • 59
  • 97
-1

This is just not possible. Stop thinking about it and look for some other problem to solve :)

It is not just that there is no way to tell the type of an argument from the binary representation of the argument. You can't even tell where to look for the argument unless you know its type.

Consider this interesting bit of Undefined Behaviour (I put the format strings into named variables to avoid the C warning about mismatched format conversions):

#include <stdio.h>
const char* good="%d %f\n";
const char* bad="%f %d\n";
int main(void) {
    int a = 42;
    double b = 375.0;
    printf(good, a, b);
    printf(bad, a, b);  /* Undefined behaviour; format mismatch */
return 0;
}

Output (on at least one popular compiler):

42 375.000000
375.000000 42

(Live on ideeone)

The second printf acts as though the arguments were in the reverse order, so they actually match the format conversions. How is that possible?

Undefined behaviour can result in anything, of course, so the observed behaviour is trivially possible. But there is an explanation:

On many architectures, the first few arguments to a function are passed in specific hardware registers rather than being pushed onto the stack. Integer arguments are passed in certain general purpose registers (in a specific order) while floating point arguments are passed in floating point registers.

So (in the second case) printf uses the stdarg.h facilities to get argument 1 as type double and argument 2 as type int. As it happens, argument 1 is type int and argument 2 is type double, but the runtime doesn't know that, so when va_arg(ap, double) is called to get argument 1, va_arg looks for the argument in the first floating point register (since it will be the first floating point argument in the list). And when the next va_arg(ap, int) is called to get argument 2, it looks for that argument in the first general purpose register used for argument passing.

rici
  • 234,347
  • 28
  • 237
  • 341