6

I read the Standard N1570 section 6.5.2.2 Function calls and was confused about the special meaning of the function type that includes prototype. Precisely 6.5.2.2(p6)

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.

6.5.2.2(p7) provides a rule of calling function with prototype:

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.

Consider the following example:

struct test_arg{
    int a;
};

void test_no_prototype(const struct test_arg a){ }

void test_with_prototype(const struct test_arg a);

void test_with_prototype(const struct test_arg a){ }

int main(){
    struct test_arg test = {.a = 42};
    test_no_prototype(test);   //1 UB?
    test_with_prototype(test); //2 Fine?
}

I think that 1 is UB because test_no_prototype does not include prototype and test has non-qualified version of struct test_arg, but the argument has type const struct test_arg which is non-compatible with struct test_arg because of different qualification.

I think that 2 is fine because test_with_prototype includes prototype and the simple assignment constraints from 6.5.16.1(p1) allow assignment of to a variable of qualified struct type from non-qualified version of the same struct.

This seems strange and for now I cannot imagine any reason of why we treat functions with and without prototype differently. Probably I understood the rule incorrectly... If so could you explain what it means?

Some Name
  • 8,555
  • 5
  • 27
  • 77
  • 3
    A function definition without prototype format would rather be the old, obsolete "K&R style" `void test_no_prototype(a) {}`. This is valid C90 but not valid ISO C. – Lundin Mar 29 '19 at 14:44

1 Answers1

7

The term prototype does not mean a declaration of a function preceding its definition. It means a declaration of a function that declares the types of its parameters (C 2018 6.2.1 2).

test_no_prototype has a prototype because void test_no_prototype(const struct test_arg a){ } declares the type of its parameter, const struct test_arg.

An example of a declaration without a prototype is void test_no_prototype();. It is an old style of declaration that should not be used in new code.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • So `void test_no_prototype()` should rather be declared as `void test_no_prototype(void)`? – Some Name Mar 29 '19 at 14:50
  • @SomeName: If `test_no_prototype` takes an argument, it should not be declared as `void test_no_prototype(void)`, because that declaration says it takes no arguments. Generally, a function should be declared the same way it is defined, although parameter names can be omitted in a declaration that is not a definition. – Eric Postpischil Mar 29 '19 at 14:51
  • Suppose I want to declare (but not define) a function that takes no parameters and provide a definition in the corresponding C file. Is there any difference between the declarations `void foo();` and `void foo(void);`? – Some Name Mar 29 '19 at 14:57
  • `6.2.1(p2)` provides a definition of that: _A function prototype is a declaration of a function that declares the types of its parameters._ So as you said _An example of a declaration without a prototype is `void test_no_prototype();`_ functions with no parameters are always without prototype.. Is that correct? – Some Name Mar 29 '19 at 15:05
  • 2
    @SomeName: `void foo(void)` declares a function that takes no parameters. This is a prototype: It is a prototype with no parameters (in terms of mathematical logic, every parameter in it has a declared type—there are no parameters, so that satisfies “every parameter has a type”). `void foo()` is a declaration without a prototype. If a function takes no parameters, you should declare it as with `void foo(void)`. Do not use `void foo()`. That is old syntax you should not use in new code. – Eric Postpischil Mar 29 '19 at 15:23