Why this C program complies and runs - c

With curiosity of the definition and scope of typedef I have written below C code in 2 .c files:
main.c
#include <stdio.h>
int main()
{
int a = 5, b = 6;
printf("a = %d, b = %d\n", a, b);
swap(&a, &b);
printf("a = %d, b = %d\n", a, b);
}
swap.c
typedef T;
void swap(T* a, T* b)
{
T t = *a;
*a = *b;
*b = t;
}
To my surprise, the code files could be compiled with Visual Studio C compiler (cl.exe /Tc main.c swap.c)
And the program runs correctly! From my understanding, typedef requires 2 parameters, but why this code compiles and runs?
For further analysis, in the main function, I declared another 2 float variables and try also to swap both after swapping the 2 integers, but this time it fails to compile (with cl.exe). What amazing is the code could be compiled and run with Tiny C (tcc.exe main.c swap.c), so it works like a template method!

Typedef is actually a declaration (it creates aliases for existing types), and is in no way limited to two 'parameters'. See Typedef Declarations (C) and Fundamental typedef operand syntax.
If you write typedef T;, you are declaring T to be an unspecified type (called "no specified type" in the C89 spec). It's a little (and only a very little, but conceptually this might help you) like #define X defines X but the preprocessor will replace it with the empty string (i.e. remove X).
So, you are typedefing T to be unspecified, which makes the arguments to your swap function of unspecified type.
What you are seeing here is that in C89 (but not C99, where it results in undefined behaviour - contrast ANSI 3.5.2 with ISOC99 6.7.2), unspecified types default to int, which is why your method works for integers but not floats in Visual Studio (presumably it disallows implicit integer typing by default). GCC will compile it with floats, though, provided you aren't using -Werror, which you probably should be.
I strongly suggest turning on some warnings: -Wall in gcc will spit out the following, among other things
swap.c:1:9: warning: type defaults to ‘int’ in declaration of ‘T’ [-Wimplicit-int]
The reason it "works" on floats is because floats and ints are probably the same size (32 bits) on your machine. Try it with double. Or char. Or short.
swap is really like this:
void swap(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}
with you calling
swap((int*)&a, (int*)&b);
Try it and compare the results for yourself.
Edit: I just tried it in tcc. -Wall does not alert you to the implicit int typing, sadly.

In C90 (which is what MSVC follows as a baseline when compiling C code), one of the possible type specifiers is (C90 6.5.2 "Type specifiers" - emphasis added):
int, signed, signed int, or no type specifiers
so if no type specifier is provided in a declaration (including a typedef) then the type defaults to int. this is commonly known as an "implicit int" declaration. Note that C99 removed support for implicit int (by default GCC only issues a warning for this when compiling in C99 mode).
Your typedef:
typedef T;
is equivalent to:
typedef int T;
So your swap() defintion is equivalent to:
void swap(int* a, int* b)
{
int t = *a;
*a = *b;
*b = t;
}
As it happens, when calling a function that hasn't been declared or prototyped (as occurs when you call swap() in main.c), the compiler will apply default argument promotions to arithmetic arguments and assume the function returns an int. Your call to swap() passes two arguments of type int*, so no promotion occurs (they're pointer arguments, not arithmetic). That happens to be exactly what the definition for swap() expects, so the function call works (and is well defined behavior).
Now, the calling code expects swap() to return an int since no declaration was seen, and your swap() function doesn't return anything (void). That is undefined behavior, but in this case there's no apparent problem (though it's still a bug in your code). However, if you change the definition of swap() so that it returns int:
int swap(int* a, int* b)
{
int t = *a;
*a = *b;
*b = t;
}
the undefined behavior goes away, even though swap() doesn't seem to return anything. Since nothing is done with the result at the call site, C90 permits the function to return with an expression. C90 allows this in order to support pre-standard code where there was no such thing as a void type.

Related

Function defined without a prototype sometimes results in conflicting types error

I have encountered a strange behaviour in clang (both AppleClang 1400.0.29.202 and clang 15.0.7 from Homebrew). Assume I have the following code:
int bar();
int bar(int a, TEST b) {
return 43;
}
It compiles with -DTEST=int but fails to compile with -DTEST=charresulting in conflicting types for 'bar'. This happens regardless of the specified standard (I have tried c89 and c99).
Am I missing something?
EDIT: I realise that omitting prototypes is highly discouraged and I don't write code like this, but there's still a lot of code written like this in the wild. I was trying to compile enscript this morning and encountered this issue.
Also, the following works with -DTEST=char.
int bar();
int bar(a, b)
int a;
TEST b;
{
return 43;
}
EDIT2: I should have tried it with other compilers before asking. GCC output contains a note about argument promotion:
note: an argument type that has a default promotion cannot match an empty parameter name list declaration
Am I missing something?
Yes, a proper function declaration. The code should have been:
int bar(int a, TEST b);
int bar(int a, TEST b) {
return 43;
}
[Why I am getting this error?]
int bar() is a function with unspecified count and type of arguments and all arguments undergo default argument promotions, where a char is promoted to an int. Because it's impossible for int bar() to take a char argument, because it would be promoted to an int, that is a conflicting type.

Pointer arithmetic on pointers to variably-modified types

Is the following valid C code? (godbolt)
#include <stddef.h>
ptrdiff_t f(size_t n, void *x, void *y)
{
if (!n) return 0;
typedef unsigned char element[n];
element *a = x, *b = y;
return a - b;
}
With -Werror=pointer-arith clang loudly complains about
<source>:8:14: error: subtraction of pointers to type 'element' (aka 'unsigned char [n]') of zero size has undefined behavior [-Werror,-Wpointer-arith]
return a - b;
~ ^ ~
while gcc compiles the code without complaint.
What is the undefined behavior that clang thinks is occuring? The
possibility of the subtraction being zero and therefore not a valid
pointer to an element of the array or something different? There's no
array access being performed, right? So that shouldn't be the case...
If the code does exhibit undefined behavior, is there a simple way to
modify the code to be fully conforming, while still using pointers to
VM-types?
The code posted is valid C code if VLAs are supported by your compiler. Note that VLAs introduced in C99 have become optional in the latest version of the C Standard.
Both gcc and clang compile the code correctly as can be verified using Godbolt's compiler explorer.
Yet clang issues a warning regarding potential undefined behavior if the value of the n argument happens to be null, failing to identify that this case has been handled with an explicit test. The problem is not the value of the subtraction but the size of the type, which would be 0 if n == 0. This warning is not really a bug, more a quality of implementation issue.
It is also arguable that a - b is only defined if both a and b point to the same array or just past the last element of it. Hence x and y must verify this constraint and have type unsigned char (*)[n] or compatible. There is an exception to this rule for accessing any type as an array of character type, so passing pointers to the same array of int would be fine, but it would be incorrect (although probably harmless) to call f this way:
int x, y;
ptrdiff_t dist = f(sizeof(int), &x, &y);
Compilers are free to issue diagnostic messages to attract the programmer's attention on potential problems, indeed such warnings are life savers in many cases for beginners and advanced programmers alike. Compiler options such as -Wall, -Werror and -Weverything are quite useful, but in this particular case, one will need to add -Wno-pointer-arith to allow clang to compile this function if -Werror is also active.
Note also that the same result can be obtained with a C89 function:
ptrdiff_t f89(size_t n, void *x, void *y)
{
if (!n) return 0;
unsigned char *a = x, *b = y;
return (a - b) / n;
}

Is it undefined behaviour in C to use pointer to a typedef type as a pointer to the primitive type?

Suppose that one has the following code:
#include <stdio.h>
typedef int myIntType;
int main()
{
int a;
myIntType *ptr = &a;
*ptr = 1;
printf("%d\n", a);
return 0;
}
Does this invoke undefined behaviour?
Under any reasonable interpretation, I expect this should simply set the value of a to 1 and therefore print the line 1 on stdout.
Compiling with gcc -Wall -Wextra -pedantic main.c -o main on Mac OS X does not produce any errors or warnings, and produces the expected output on execution.
My question is purely theoretical, and I do not intend to use such a construct in real-life code.
From cppreference.com about the typedef specifier:
The typedef specifier ... may declare array and function types, pointers and references, class types, etc. Every identifier introduced in this declaration becomes a typedef-name, which is a synonym for the type of the object or function that it would become if the keyword typedef were removed.
In other words, it is the exact same as if you removed your custom type and replaced it with int. Thus, it is well-defined behavior. int and myIntType are 100% interchangeable.
That is actually a c++ reference. From K&R, chapter 6.7 on typedef:
C provides a facility called typedef for creating new data type names. For example, the declaration typedef int Length; makes the name Length a synonym for int. The type Length can be used in declarations, casts, etc., in exactly the same ways that the type int can be.
Do be aware that K&R isn't the most up-to-date standard. The other answer cites the current standard. As far as I know, typedef hasn't changed.
In your example, myIntType * is the same as int *.
Section 6.7.8 of the C standard gives an example of this:
4 EXAMPLE 1 After
typedef int MILES, KLICKSP();
typedef struct { double hi, lo; } range;
the constructions
MILES distance;
extern KLICKSP *metricp;
range x;
range z, *zp;
are all valid declarations. The type of distance is int, that of
metricp is "pointer to function with no parameter specification
returning int", and that of x and z is the specified structure; zp is
a pointer to such a structure. The object distance has a type
compatible with any other int object.

Passing float to a function with int argument (that is not declared beforehand)

I have read Garbage value when passed float values to the function accepting integer parameters answers. My question goes a bit deeper. I could have also asked there had I more than 50 reputation point. I am adding my code for more clarification:
#include <stdio.h>
#include <string.h>
void p2(unsigned int tmp)
{
printf("From p2: \n");
printf("tmp = %d ,In hex tmp = %x\n", tmp, tmp);
}
int main()
{
float fvar = 45.65;
p1(fvar);
p2(fvar);
printf("From main:\n");
printf("sizeof(int) = %lu, sizeof(float) = %lu\n", sizeof(int),
sizeof(float));
unsigned int ui;
memcpy(&ui, &fvar, sizeof(fvar));
printf("fvar = %x\n", ui);
return 0;
}
void p1(unsigned int tmp)
{
printf("From p1: \n");
printf("tmp = %d ,In hex tmp = %x\n", tmp, tmp);
}
The output is:
From p1:
tmp = 1 ,In hex tmp = 1
From p2:
tmp = 45 ,In hex tmp = 2d
From main:
sizeof(int) = 4, sizeof(float) = 4
fvar = 4236999a8
Passing a float value to a function that is declared beforehand (i.e. p2) with int arguments gives the correct result. When trying the same with a function that is not declared beforehand (i.e. p1) gives incorrect values. And I know the reason that compiler won't assume any type or arity of arguments for the function not declared before handed. That's why float value does not get typecasted to int in the case of p2.
My confusion is, in the case of p2, how exactly does float value get copied to local int variable tmp.
If it is 'bit by bit copy' than reading those locations should yield something (except 1) in hex at least (if not in integer). But that does not sound the case as output shows. I know that float representation is different.
And how p2 may read registers/stack locations that floats weren't copied to? as simonc suggested in the linked question?
I have included the size of int and float both and my compiler is gcc if that helps.
The C programming language is essentially a single-scan language - a compiler doesn't need to reread the code but it can assemble it line by line, retaining information only on how identifiers were declared.
The C89 standard had the concept of implicit declaration. In absence of a declaration, the function p1 is declared implicitly as int p1(); i.e. a function that returns an int and takes unspecified arguments that go through default argument promotions. When you call such a function giving it a float as an argument, the float argument is promoted to a double, as called for by default argument promotions. It would be fine if the function was int p1(double arg); but the expected argument type is unsigned int, and the return value is not compatible either (void vs int). This mismatch will cause the program to have undefined behaviour - there is no point in reasoning what is happening then. However, there are many old C programs that would fail to compile, if the compilers wouldn't support the archaic implicit declarations - thus you just need to consider all these warnings as errors.
Notice that if you change the return value of p1 into an int, you will get less warnings:
% gcc implicit.c
implicit.c:14:5: warning: implicit declaration of function ‘p1’ [-Wimplicit-function-declaration]
p1(fvar);
^~
But the observed behaviour on my compiler would be mostly the same.
Thus the presence of mere warning: implicit declaration of function ‘x’ is quite likely a serious error in newly written code.
Were the function declared before its use, as is case with p2, then the compiler knows that it expects an unsigned long as the argument, and returns void, and therefore it would know to generate correct conversion code from float to unsigned long for the argument.
The C99 and C11 do not allow implicit function declarations in strictly-conforming programs - but they also do not require a conforming compiler to reject them either. C11 says:
An identifier is a primary expression, provided it has been declared as designating an object (in which case it is an lvalue) or a function (in which case it is a function designator).
and a footnote noting that
Thus, an undeclared identifier is a violation of the syntax.
However, it doesn't require a compiler to reject them.
This,
void p1(unsigned int tmp);
would be implicitly declared as
int p1();
by the compiler.
Although the compiler does not throw an error, it should be considered one as you can read in the linked post.
In any event, this is undefined behavior and you can't expect a predictable output.
In binary level, float and int don't look alike at all.
When trying to copy a float into a int, there's an implicit conversion, that's why when you call a function that takes int as argument but you provide a float you get the integer part of it, but in the final test you get to see how ugly it really look like. That's no garbage, that's how a float looks like in memory if you'd print it in hexadecimal. See IEEE 754 for details.
The issue with p1() however is that you are trying to call a function that has not been declared, so it's automatically declared as int p1(). Even though you later define it as void p1(unsigned int tmp), it has already been declared as int p1() (not taking any parameters) so it doesn't work (behavior is undefined). I'm pretty sure the compiler is screaming with warnings and errors about that, those errors aren't meant to be ignored.
Notice there's a big difference between declaring and defining a function. It is perfectly legal to define a function later, the way you are doing, but if you want it to work properly it has to be declared before any attempt to use it.
Example:
// declare functions
void p1(unsigned int tmp);
void p2(unsigned int tmp);
// use functions
int main()
{
p1(1);
p2(1);
}
// define functions
void p1(unsigned int tmp)
{
// do stuff
}
void p2(unsigned int tmp)
{
// do stuff
}

C: type conversion when passing an argument on a function call

From The C Programming Language 2nd Edition:
Since an argument of a function call is an expression, type conversions also take place when arguments are passed to function. In absence of a function prototype, char and short become int, and float becomes double.
By reading the text, I am getting an impression that unless you explicitly specify the argument type by either using cast or function prototype, function arguments will always be passed as either passed as int or double.
In order to verify my assumption, I compiled the following code:
#include <stdio.h>
main()
{
unsigned char c = 'Z';
float number = 3.14f;
function_call(c, number);
}
void function_call(char c, float f)
{
}
After compilation I get the following warnings:
typeconversion.c:11: warning: conflicting types for ‘function_call’
typeconversion.c:7: warning: previous implicit declaration of ‘function_call’ was here
My guess is c and number were both converted to int and double on the function call, and were then converted back to char and float. Is this what actually happened?
Casts are irrelevant, it's the (possibly implicit) prototype that matters.
void foo(short s) {
// do something
}
int main(void) {
signed char c = 'a';
foo(c); // c is promoted to short by explicit prototype
bar(c); // c is promoted to int by implicit prototype
}
void bar(int i) {
// do something
}
When the book says "an argument of a function call is an expression" it means that the same type promotion rules apply. It might be easier to understand if you think of a function argument as an implicit assignment to the variable specified in the function prototype. e.g. in the call to foo() above there's an implicit short s = c.
This is why casts don't matter. Consider the following code snippet:
signed char c = 'a';
int i = (short) c;
Here the value of c is promoted first to short (explicitly) then to int (implicitly). The value of i will always be an int.
As for char and short becoming int and float becoming double that refers to the default types for implicit function prototypes. When the compiler sees a call to a function before it has seen either a prototype or the definition of the function it generates a prototype automatically. It defaults to int for integer values and double for floating-point values.
If the eventual function declaration doesn't match to implicit prototype, you'll get warnings.
You've got the general idea of what's wrong, but not exactly.
What happened is that when you wrote
function_call(c, number);
The compiler saw that you were calling a function that it had not seen yet, and so had to decide what its signature should be. Based on the promotion rule you quoted earlier, it promoted char to int and float to double and decided that the signature is
function_call(int, double)
Then when it sees
function_call(char c, float f)
it interprets this as a signature for a different function with the same name, which is not allowed in C. It's exactly the same error as if you prototype a function differently from how you actually define it, just that in this case the prototype is implicitly generated by the compiler.
So, it is this rule that's causing the issue, but the error doesn't have anything to do with actually converting values back and forth between types.
The compiler is complaining that it assumed function_call is an int returning function as indicated by the standard, and then you tell it is a void function. The compiler won't care about arguments unless you are explicitely declaring them to be different to the actual function. You can pass it no arguments and it won't complain.
You must always declare your functions because this error will go undetected if the function is in other modules. If the function should return any type that could be larger than int such as void* or long, the cast to int in the caller function will most likely truncate it, leaving you with a weird bug.
Everyone has missed one thing. In ISO C an ISO-syntax prototype overrides default argument promotion.
And in that case the compiler is allowed to generate different code (!) based purely on the style of definition. This gets you K&R compatibility, but you can't always call between the language levels unless you have written the ISO code as K&R expects or modified the K&R code to see the ISO prototypes.
Try it with cc -S -O ...
extern float q; void f(float a) { q = a; }
movl 8(%ebp), %eax
movl %eax, q
extern float q; void f(a) float a; { q = a; } // Not the same thing!
fldl 8(%ebp)
fstps q

Resources