13

I found myself in the following situation:

#include <stdio.h>

typedef struct T1 { int id; } T1;  
typedef struct T2 { int id; } T2;

void f(T1 *ptr) { printf("f called\n"); }

int main(void) 
{
    T2 obj; 
    T2 *ptr = &obj; 
    f(ptr); // shouldn't this be a compilation error ? 
    return 0;
}

of course, this is invalid C++, but in C, the program prints "f called". How is this valid ?

EDIT

(Just in case it's unclear) The program would still compile and run if T2 was "structurally" different, eg

typedef struct T2 { double cc[23]; } T2;
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
  • 6
    If you compile with the settings that treat warnings like errors, the code does not compile ([link](http://ideone.com/jVAysY)). – Sergey Kalinichenko Nov 25 '14 at 13:26
  • see *similar* question here [Casting one struct pointer to other - C - Stack Overflow](http://stackoverflow.com/questions/3766229/casting-one-struct-pointer-to-other-c/3766251#3766251) – Wolf Nov 25 '14 at 13:27
  • 1
    Interesting. Have you tried it with two structures that are **really** different? – barak manos Nov 25 '14 at 13:30
  • It is not valid. An implementation is not prohibited from executing an invalid program. It is only required to diagnose (some) invalid programs. Have your implementation issued a diagnostic? – n. m. could be an AI Nov 25 '14 at 13:48
  • 2
    Some compilers only give warnings for things that are actual errors. – gnasher729 Nov 25 '14 at 13:49
  • 1
    @gnasher729: Yes, and the C standard permits this. It only requires at least one diagnostic (which can be a non-fatal warning) for any program that violates a syntax rule or constraint. The only time a compiler is *required* to reject a translation unit is when it contains a `#error` directive that survives preprocessing. – Keith Thompson Nov 25 '14 at 18:39

4 Answers4

11

This is not valid, if you want to force standard compliant code it is important to compile with the correct flags for example both gcc and clang the following flags:

-std=c99 -pedantic-errors

will generate an error for diagnostics required by the C99 standard and similarly you could use -std=c11 for C11. This will generate the following error from gcc (see it live):

error: passing argument 1 of 'f' from incompatible pointer type

Compilers have extensions and allow features like implicit int due to legacy code and it is important to know the difference. See gcc document: Language Standards Supported by GCC for more details.

The quick way to see that this is actually invalid is to go to the Rationale for International Standard—Programming Languages—C which tell us in section 6.3.2.3 Pointers which deals with conversions that:

It is invalid to convert a pointer to an object of any type to a pointer to an object of a different type without an explicit cast.

The slightly longer path requires we go to the draft C99 standard section 6.5.2.2 Function calls which says (emphasis mine going forward):

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,

and if we then go to section 6.5.16 Assignment operators which says:

One of the following shall hold

and for pointers we have:

  • both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;
  • one operand is a pointer to an object or incomplete type and the other is a pointer to a qualified or unqualified version of void, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;
  • the left operand is a pointer and the right is a null pointer constant;

we see that none of these cases hold and therefore the conversion is invalid.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • "will generate an error for violations of the C99 standard" -- Almost, but not exactly. `-pedantic-errors` will turn all standard-mandated diagnostics into an error. Other warnings stay a warning, even warnings that happen to point out that the program is not valid C99. –  Nov 25 '14 at 21:13
6

When compiling, I get the following warning:

temp.c: In function ‘main’:

temp.c:20:5: warning: passing argument 1 of ‘f’ from incompatible pointer type [enabled by default]

temp.c:13:6: note: expected ‘struct T1 *’ but argument is of type ‘struct T2 *’

It's "valid" because they're both pointers, and hence can be converted, it's just not a good idea.

Community
  • 1
  • 1
slugonamission
  • 9,562
  • 1
  • 34
  • 41
2

As per the C99 standard, section 6.5.2.2 [Function calls], paragraph 7, this is valid allowed in C, but not valid.

For example, in your code, if T1 and T2 are different structures having different elements, and the address of T2 is passed to f() and accepted as T1*, then this is absolutely wrong and the result is fatal. Just in case it got compiled [it should have produced warnings about the passing argument <number> of <a function> from incompatible pointer type], doesn't obviously mean it is right.

In your code, as you are not accessing the structure variables inside f(), due to the compilar optimization, the warnings might have been disappeared.

It reads

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. 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.

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
  • 2
    Can you please add some quoted text from the spec? I looked at the draft but the numbering doesn't match. – unwind Nov 25 '14 at 13:31
  • @unwind Sir, in my docmument it is on page 72. However, i'm not much knowledgable, please correct me if i'm wrong. – Sourav Ghosh Nov 25 '14 at 13:40
  • 3
    I don't see how the quoted text relates to the code in question, at all. – unwind Nov 25 '14 at 13:42
  • 3
    "as if by assignment". What makes yhe corresponding assignment valid? – n. m. could be an AI Nov 25 '14 at 13:43
  • @n.m. the assignment is not valid, but the conversion is valid. am i right? – Sourav Ghosh Nov 25 '14 at 13:46
  • @unwind maybe i chose the wrong words. I meant to say it is _allowed_, but not _valid_. is that right? – Sourav Ghosh Nov 25 '14 at 13:47
  • The standard says "converted as if by assignment". This means that the conversion is exactly as valid as the assignment. – n. m. could be an AI Nov 25 '14 at 13:53
  • 2
    The standard does not have a separate class of invalid but allowed constructs. – n. m. could be an AI Nov 25 '14 at 13:54
  • @n.m. Sir, i'm not sure if i have understood completely, but what I thought is because of the (invalid but allowed) implicit conversion, OP's code prints the output. if `T1` and `T2` are _really_ different structure, then this is perfectly invalid. Is that right? – Sourav Ghosh Nov 25 '14 at 14:01
  • @NikosAthanasiou if you're not accessing the structures inside the called function, maybe the error is suppressed [due to compilar optimization]. check [this one](http://ideone.com/OfDkg8), a simple alteration of your code. – Sourav Ghosh Nov 25 '14 at 14:22
  • The standard doesn't have an "invalid but allowed" category of programs. An implementation can attempt to run an invalid program, nothing prevents that. It only has to issue diagnostics for diagnosable errors, which it does. If a program is invalid, and an implementation is trying to run it anyway, you cannot find a justification for that in the standard. – n. m. could be an AI Nov 25 '14 at 15:12
1

As others have mentioned, in C this code requires a diagnostic (and apparently you got one, in the form of a gcc warning).

You can "fix" the code with a cast:

f( (T1 *)ptr );

In your sample program this is fine. However, in a more complicated program there would be a problem. Since T1 and T2 are not compatible types , then having f write through the pointer and then reading through ptr (or vice versa) would be a strict-aliasing violation.

You can work around the issue though, taking advantage of the fact that unions can be used for aliasing in C, and that there is special provision for union members being structs with a common initial sequence:

union
{
    T1 t1;
    T2 t2;
} obj;

f( &obj.t1 );                 // might write to ptr->id
printf("%d\n", obj.t2.id);    // OK, writes int that f wrote

Since the union example has to work, any sane compiler would just use a common layout for T1 and T2 and not do any strict-aliasing optimilzations here, so the code with the cast could be reasonably expected to "just work", as you see in your example run.

M.M
  • 138,810
  • 21
  • 208
  • 365