Asked  6 Months ago    Answers:  5   Viewed   44 times

Setup

I have a few questions about the default argument promotions when calling a function in C. Here's section 6.5.2.2 "Function calls" Paragraphs 6, 7, and 8 from the C99 standard (pdf) (emphasis added and broken into lists for ease of reading):

Paragraph 6

  1. If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions.
  2. If the number of arguments does not equal the number of parameters, the behavior is undefined.
  3. If the function is defined with a type that includes a prototype, and either the prototype ends with an ellipsis (, ...) or the types of the arguments after promotion are not compatible with the types of the parameters, the behavior is undefined.
  4. If the function is defined with a type that does not include a prototype, and the types of the arguments after promotion are not compatible with those of the parameters after promotion, the behavior is undefined, except for the following cases:
    • one promoted type is a signed integer type, the other promoted type is the corresponding unsigned integer type, and the value is representable in both types;
    • both types are pointers to qualified or unqualified versions of a character type or void.

Paragraph 7

  1. If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type.
  2. The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.

Paragraph 8

  1. No other conversions are performed implicitly; in particular, the number and types of arguments are not compared with those of the parameters in a function definition that does not include a function prototype declarator.

What I know

  • The default argument promotions are char and short to int/unsigned int and float to double
  • The optional arguments to variadic functions (like printf) are subject to the default argument promotions

For the record, my understanding of a function prototype is this:

void func(int a, char b, float c);  // Function prototype
void func(int a, char b, float c) { /* ... */ }  // Function definition

Question

I'm having a really hard time groking all of this. Here are some questions I have:

  • Do prototyped and non-prototyped functions' behavior really differ so much, such as with regard to default promotions and implicit conversions?
  • When do default argument promotions occur? Is it always? Or is it just in special cases (like with variadic functions)? Does it depend on whether a function is prototyped?

 Answers

58

Upvoted AProgrammer's answer—those are the real goods.

For those of you who are wondering why things are this way: in the dark ages before 1988, there was no such thing as a function prototype in classic "K&R" C, and the default argument promotions were instituted because (a) there were essentially "free", as it costs no more to put a byte in a register than to put a word in a register, and (b) to cut down on potential errors in parameter passing. That second reason never quite cut it, which was why the introduction of function prototypes in ANSI C was the single most important change ever in the C language.

As to when default promotions kick in: default argument promotions are used exactly when the expected type of the argument is unknown, which is to say when there's no prototype or when the argument is variadic.

Tuesday, June 1, 2021
 
RenegadeAndy
answered 6 Months ago
20

No, Standard C does not support either. Why do you feel you need to convert your C++ code to C? That could get quite tricky - I'd have thought writing wrappers would be the way to go, if your C++ must be callable from C.

Thursday, July 29, 2021
 
skrilled
answered 4 Months ago
69

In javascript you can call a function (even if it has parameters) without parameters.

So you can add default values like this:

function func(a, b){
   if (typeof(a)==='undefined') a = 10;
   if (typeof(b)==='undefined') b = 20;

   //your code
}

and then you can call it like func(); to use default parameters.

Here's a test:

function func(a, b){
   if (typeof(a)==='undefined') a = 10;
   if (typeof(b)==='undefined') b = 20;

   alert("A: "+a+"nB: "+b);
}
//testing
func();
func(80);
func(100,200);
Saturday, August 7, 2021
 
Gregosaurus
answered 4 Months ago
90

Define all the functions so they take a single array argument.

Comment from Barmar

Unifying all functions to the same prototype is exactly what one normally does in this case, though I'd go with a prototype with two parameters: A pointer to an array with the real parameters as well as it's size. That way not every function has to split/parse its arguments on its own.

I really like stuff like this, so I made a short demo. I made this on my mobile, so it's a bit rough and would need some improvements if used in the wild (memory management and error detection for example). Here it is:

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

// a node in the abstract syntax tree. Either a
// value or a call
struct Ast {
  bool isCall;
  union {
    int value;
    struct {
      char const * operator;
      size_t countOperands;
      struct Ast * operands;
    } call;
  };
};

// unified function type. Could've also passed an
// int array, but then evaluate would've needed
// a memory allocation, so ...
typedef int (*Function)(struct Ast *, size_t);


// implementation of + function. Sums the values of
// parameters. (which are hopefully evaluated)
int sum(struct Ast * parameters, size_t num) {
  int result = 0;
  while (num > 0) {
    --num;
    result += parameters [num]. value;
  }
  return result;
}

// implementation of ? function, ignores any
// parameters and just asks for an integer.
int ask (struct Ast * parameters, size_t num) {
  int value;
  scanf("%d", & value);
  return value;
}

// poor man's lookup table
static Function const functions [] = {sum, ask};
static char const * const function_names [] = {"+", "?"};

// poor man's lookup from above static arrays
Function lookup (char const * name) {
  size_t it = sizeof (functions) / sizeof (functions [0]);
  while (it > 0) {
    --it;
    if (strcmp(name, function_names [it]) == 0) {
      return functions [it];
    }
  }
  exit(1);
}

// evaluate an Ast. Normally one wouldn't return
// an Ast node but rather some value_t (assuming
// dynamic typing)
// this function is also destructive on call Ast nodes,
// in order to get around any memory management.
// so be careful!
struct Ast * evaluate (struct Ast * node) {
  if (! node->isCall) {
    // anything that's not a call is a value, thus
    // self evaluating, return it unchanged!
    return node;
  }
  // so it's a call. Get the associated function from
  // the lookup table!
  Function f = lookup(node->call.operator);
  // unconditionally evaluate all operands of the call.
  // thus no macros or conditionals, sorry!
  size_t o;
  for (o = 0; o < node->call.countOperands; ++o) {
    // destructive!
    node->call.operands[o] = *evaluate(&(node->call.operands[o]));
  }
  // use the call node to store the result value.
  // this will blow up if any call node uses any
  // allocated memory!
  node->isCall = false;
  // call the function with the evaluated operands and
  // store the result
  node->value = f(node->call.operands, node->call.countOperands);
  return node;
}

int main () {
  // I didn't want to write a parser, so here's a
  // static Ast of (+ 21 10 (?))
  struct Ast nodes [] = {
    {.isCall=false, .value=21},
    {.isCall=false, .value=10},
    {.isCall=true, .call = {
        .operator="?", .countOperands=0}},
    {.isCall=true, .call = {
        .operator="+", .countOperands=3,
        .operands=nodes}}};
  struct Ast * result = evaluate(&(nodes [3]));
  printf("(+ 21 10 (?)) => %dn", result->value);
  return 0;
}

Written and "tested" on ideone.

A different approach would be to use a void * tagged with some function type information. But it's rather difficult to pass the actual parameters to functions encoded like that, and it also doesn't scale well.

Sunday, August 22, 2021
 
pawello2222
answered 4 Months ago
65

Yes, it's valid. In this example, ... creates a variadic function using the va_list mechanism. This is how variadic functions are implemented in C, and to some degree in C++ (though C++11's template parameter packs have rendered this mechanism obsolete).

Further reading: va_arg

Friday, September 3, 2021
 
zi3guw
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share