Is it safe to do something like foo(x, &x)? - c

Note, I would not write code like this. I'm just curious, and it would help me writing a better answer for another question. But let's say that we have this function:
void foo(int a, int *b)
{
*b = 2*a;
}
And call it like this:
int x=42;
foo(x, &x);
Apart from the fact that it is a very strong code smell, can this cause any real problems? Is it UB or does it violate any rules in the C standard?

This
int x=42;
foo(x, &x);
is a well-formed code. The order of the evaluation of arguments is not important in this case.
In fact the function call is equivalent to
foo( 42, &x);
because the first argument is passed by value.

The code is OK, but to dig a bit deeper between the lines to see what special case situations exist, which ones that are fine and which ones that could potentially cause problems:
The order of evaluation of function arguments is unspecified and unsequenced, but it does not matter in this specific case.
Function arguments are evaluated before passed to a function, then there is a sequence point after the evaluation. Meaning that all calculations and side effects in the arguments occur before the function is called (but in an unspecified order in relation to each other).
So even (artificial crap) code such as this is actually well-defined:
void foo(int a, int *b)
{
printf("%d\n", a); // prints 42
*b = 2**b; // gives 43 * 43 = 86
}
...
int x=42;
foo(x++, &x);
The x++ vs &x is fine since &x is not a value computation of the object. And thanks to the sequence points, the x++ occurs before the calculation inside the function, so that part is also well-defined.
The parameter a inside the function is of course a local copy of caller-side x, so the function can do what it pleases with that one.
Had it been two pointers pointing at the same object, then they would "overlap" and that's undefined behavior in some cases, depending on what the function does. For example memcpy(&x, &x, sizeof x); is undefined behavior.
There is a sequence point at each ; and also at the end of the function.
For variables declared at file scope, the function must assume that pointer parameters modifying the pointed-at value might modify the file scope variable. So in case of this code:
void foo(int a, int *b)
{
extern int x;
x = 2*a;
printf("%d\n", *b);
}
the compiler must fetch the value of *b after the assignment to x, because it can't assume that b doesn't point at x - the pointer could be an alias of &x. And here's where the various rules of pointer aliasing comes in.
Similarly, two pointer parameters of the same type might point at the same object in the caller code and the compiler isn't allowed to assume that they don't, unless we manually add a restrict qualifier to them.

The question is already well answered, but the above would work even without being in a separate function due to evaluation order.
Even the following code would work fine.
void foo(int *a, int *b) {
*b = 2*(*a);
}
int x = 42;
foo(&x, &x);
and x would contain 84 after it executes. The right-hand side of the assignment is evaluated before the left-hand side (that's why we can do x = x+1; and drive mathematicians crazy :) )

Related

Is C language call by reference?

I know that there is no Call by reference in C language.
but, Some people say that there is a Call by reference in C.
I'm confused.
As far as I know, when handing over the factor to the function in C, I know that the value transferred to the function is received by making a local copy as a parameter.
However, in C++, "Call by reference" is possible because "the same element that differs only from the factor and name" is created by the reference "&". Is that true?
I know that there is no Call by reference in C language.
Correct. C always passes by value.
Some people say that there is a Call by reference in C. I'm confused.
They are wrong. This is very easy to test.
Let's start by looking at this small Perl program.
use 5.014;
sub f {
$_[0] = 456; # $_[0] is the first argument.
}
my $x = 123;
f( $x );
say $x; # 456
Changing the parameter changed the argument. This is an example of pass by reference. Perl arguments are passed by reference.
Now let's do the same thing in C.
#include <stdio.h>
void f( int x ) {
x = 456;
}
int main( void ) {
int x = 123;
f( x );
printf( "%d\n", x ); // 123
}
Changing the parameter had no effect on the argument. This is an example of pass by value. C's arguments are passed by value.
You can use pointers to achieve a similar result.
#include <stdio.h>
void f( int *xp ) {
*xp = 456;
}
int main( void ) {
int x = 123;
f( &x );
printf( "%d\n", x ); // 456
}
Note that the argument (the pointer) is still passed by value. Changing xp itself (as opposed to *xp) has no effect on the caller.
Same goes for arrays. The degenerate into a pointer which is passed by value.
#include <stdio.h>
void f( char a[] ) {
a = "def";
}
int main( void ) {
char a[] = "abc";
f( a );
printf( "%s\n", a ); // abc
}
This could be called passing a reference. It is not passing by reference, however.
However, in C++, "Call by reference" is possible
Correct.
C++ normally uses pass by value.
#include <iostream>
using namespace std;
void f( int x ) {
x = 456;
}
int main( void ) {
int x = 123;
f( x );
cout << x << endl; // 123
}
But pass by reference can be requested using &.
#include <iostream>
using namespace std;
void f( int &x ) {
x = 456;
}
int main( void ) {
int x = 123;
f( x );
cout << x << endl; // 456
}
I know that there is no Call by reference in C language. but, Some people say that there is a Call by reference in C. I'm confused.
Your confusion is, I'm afraid, inevitable, but it's not your fault.
People have been arguing about this question for a long time. (There's an FAQ list entry on the question that dates to the 1990's.) I didn't realize it was still a matter of debate, but evidently it is, because the same confusion you've experienced has been repeated (which is to say, validated) in the answers posted right here on this Stack Overflow question, where you hoped you'd get a definitive answer.
Depending on how you define your terms, all of the following statements are more or less true:
C does not have pass by reference. C always passes arguments by value.
C lets you simulate pass by reference, by passing a pointer instead. But the pointer is passed by value.
Arrays in C are passed by reference, because the array reference in the function call decays into a pointer to the array's first element. (But the pointer is passed by value.)
C++ has reference parameters, meaning that the programmer doesn't have to explicitly use the & operator in the call, or the * operator in the function. (But the implementation of that reference involves something very much like a pointer which is, again, passed by value.)
Or, in other words, for a sufficiently formal and restrictive definition of the term "pass by reference", C does not have it. But it has a couple of things that are pretty close, perhaps close enough to satisfy a less-formal definition, or to let you say, "You can get something a lot like call-by-reference in C, if you want."
C sometimes appears to pass by reference. The detail is that the argument goes through automatic conversions and that argument is passed by value. The effect is that the original argument experiences a pass-by-reference.
When an argument is a function, it may look/act like it is pass by reference. Functions are converted to the address of a function. The function sqrt is not passed by value. foo() receives the address of the function as a function pointer.
foo(sqrt);
An array looks like it is passed by reference in that bar() does not receive the value of the array. What happened under the table is that the array is converted to the type and address of the first element and bar() receives a pointer to a char. bar() may do things that change s[], exactly what pass by refence does in other languages, yet in C there is a technical under-the-table conversion that maintains the idea of pass only by value.
char s[1];
bar(s);
I know that there is no Call by reference in C language. but, Some
people say that there is a Call by reference in C. I'm confused.
The traditional definition of "pass by reference" is an aspect of subprogram calling semantics providing that the subprogram's parameters are bound to the same objects that are designated by the caller as the corresponding subprogram arguments.1,2 This has the effect that if the subprogram modifies the object identified by one of its parameters, including by assignment, then the caller can observe that modification (provided that the caller retains a way to examine that object). This is the typical implementation of Fortran's call semantics, among others.
For example, consider a program of this form (expressed in a polyglot pseudocode):
subprogram sub1(x)
x = 0
end
integer a
a = 42
call sub1(a)
print(a)
In a language with pass-by-reference semantics, the assignment to x in sub1 will modify the value of a in the caller, with the result that the program prints "0".
Pass-by-value is the main alternative: the names of subprogram parameters are not bound to the objects specified by the caller. They are instead bound to different objects with (initially) the same values as those presented by the caller. In a language with pass-by-value semantics, a program such as the above would be expected to print "42", as the subprogram's parameter x refers to a different object than the caller's a, therefore the subprogram's assignment to x is not visible to the caller.
As far as I know, when handing over the factor to the function in C, I
know that the value transferred to the function is received by making
a local copy as a parameter.
Yes, this is mandated by the C language specification:
An argument may be an expression of any complete object type. In
preparing for the call to a function, the arguments are evaluated, and
each parameter is assigned the value of the corresponding argument.
(C17 6.5.2.2/4; emphasis added)
As judged via the definitions above, this is unequivocally pass-by-value in all cases. However, there are a couple of cases that require special attention in this context:
One can pass the address of an object to a function -- for example, by means of the unary & operator. In that case, the function can modify the pointed-to object via the pointer it receives. Some people are inclined to characterize this as pass-by-reference, but it does not satisfy the definition above, because the argument was never the pointed-to object in the first place. Moreover, assignment to the received pointer itself does not modify the argument presented by the caller or the object to which it points.
The arguments presented by the caller are the results of evaluating the expressions presented, and C has some cases where the effect of that may be surprising to the uninitiated, especially
functions. Wherever the name of a function appears in a valid expression that is evaluated, it is automatically converted to a pointer.3 In particular, when the name of a function appears in the argument list of a function call, the corresponding parameter receives a pointer to the function. The called function can use that pointer to call the function it points to, even if that function's identifier is not in scope. But the function has not been passed by reference (by the above definition), for if the called function assigns a new value to the parameter, that does not modify the function it originally pointed to (nor the caller's copy of the function pointer, if it retains one).
arrays. C specifies that with only a few, narrow exceptions, expressions of array type are automatically converted to pointers. I think it's fair and consistent to describe that as the result of evaluating a (sub)expression of array type being a pointer to the first element of the array. The argument lists to function calls are no exception, so when you specify an array as a function argument, the corresponding function parameter receives a pointer to the first array element.
As a result, the called function can modify the array's elements via the pointer it receives. Some people describe that effect as the array having been passed by reference, but it doesn't actually satisfy the above definition. The parameter doesn't even have the same type as the caller's array, and moreover, if you assign a new value to the parameter itself then the effect is visible only in the function. In this sense, modifying array elements via a pointer received as a parameter is analogous to calling a function via a function pointer received as a parameter.
However, in C++, "Call by reference" is possible because "the same
element that differs only from the factor and name" is created by the
reference "&". Is that true?
Yes, one of the things that C++ has that C does not is references, and one of the major uses of references is providing pass-by-reference semantics that satisfy the above definition. It's not quite pass-by-reference in the Fortran sense because the parameter has a different type than the corresponding argument, but for most purposes, the parameter can be used in the same ways, and with the same effects, as the argument to which it is bound. In particular, assignment to a reference does affect the referenced object.
C++ references have some additional properties that differentiate them from pointers, among them:
A reference can be created only from another, valid object, either as a reference to that object or, if that object is itself a reference, as a copy of that reference (referring to the same object).
References cannot be rebound to different objects.
These play well with using C++ references for pass-by-reference.
Until now I have grounded my discussion in the definition given above, but it will be clear from the answers and comments given that there is a controversy here over whether that remains an appropriate definition. Some claim that the language has moved on, and in particular that in the context of C, the term "pass by reference" has become accepted as including passing a pointer. To be sure, some do use the term that way. On the other hand, "accepted" is clearly too strong a term, because plenty of others, including some voicing their opinions here, insist that it is is imprecise, sloppy, or simply wrong to describe passing a pointer to an object as passing that object by reference.
One thing to consider here is that the conventional meaning of these terms in context of most programming languages other than C has not appreciably moved from the traditional ones, even for much younger languages. C++ in particular is relevant because of the shared history and strong interoperability of these, and no C++ programmer would characterize passing a pointer as pass by reference. But the terminology is also well established in Java, which has only pass by value, including passing references by value, but not pass by reference. It also comes up in Python, which is like Java but more so, because all argument passing there is passing references by value. The distinction is important for explaining the semantics of those languages, as indeed it is for C, too.
Therefore, at present, if
you are engaging in comparative analysis of computer languages,
you want to express your ideas with maximum precision, or
you want to avoid, in many cases, earning a point of disrespect from a portion of your audience
then you will avoid conflating passing pointers with pass by reference. But if you do conflate the two then you can reasonably expect to be understood, at least in C-specific context.
1 Wikipedia definition
2 Strongly supported Stack Overflow definition
3 Including in their most common context, function calls: "The expression that denotes the called function shall have type pointer to function" (C17 6.5.2.2/1).
But, Some people say that there is a Call by reference in C. I'm confused.
This depends on how you define the term "call by reference":
Three years ago, I worked for a company in the automotive industry:
Pointer arguments that were pointing to a (single) variable (and not to an array) of the type someType were neither defined as "arguments of the type someType *" nor as "pointers to a someType" but as "'call by reference' arguments of the type someType".
You could also see this at the "[out]" or "[inout]" tag in the doxygen comments:
/*! \brief Example function (non-AUTOSAR)
* \param[in] foo Value of the "foo" force
* \param[out] bar Value of the "bar" speed
* \param[inout] foobar Current value of the "foobar" state
* \return True if calculating "bar" succeeded */
bool someFunction(someType foo, someType * bar, someType * foobar)
{
...
}
Microsoft's documentation of the Windows API seems to see this in a similar way - for example here:
BOOL GetExitCodeProcess(
[in] HANDLE hProcess,
[out] LPDWORD lpExitCode
);
“Pass by reference”1 is an old computer programming phrase that antedates the “reference” feature built into C++. It means providing a pointer to an object. The C standard says a pointer “provides a reference” (C 2018 6.2.5 20). References can be provided either manually by a programmer writing some explicit notation or by a feature built into a programming language.
When C++ developers named a new feature a “reference,” that did not change the prior usage of the term. When discussing “passing by reference” in languages other than C++ or others that provide built-in references, the phrase has its classic meaning.
C supports passing by reference (manually) but does not provide it as a built-in feature, except to the extent that arrays and functions undergo automatic conversions and adjustments that effect passing by reference. Programmers understand from context that “pass by reference” refers to passing a pointer in C and passing a built-in reference type in C++.
Footnote
1 The question uses the phrase “call by reference,” but this is an imprecise use of terminology. Whether we are discussing C++ or C, the issue is how arguments are passed, not how functions are called.
In C, all function arguments are passed by value, meaning each argument expression in the function call is fully evaluated and the result of that evaluation is passed to the function:
#include <stdio.h>
void swap( int a, int b )
{
int tmp = a;
a = b;
b = tmp;
}
int main( void )
{
int x = 10, y = 10;
printf( "Before swap: x = %d, y = %d\n" );
swap( x, y );
printf( " After swap: x = %d, y = %d\n" );
return 0;
}
The formal parameters a and b in the definition of swap are different objects in memory from the actual parameters x and y in main. The expressions x and y are fully evaluated, and the results of those evaluations (the values 10 and 20) are copied to a and b. Changing the values of a and b has no effect on x or y, and the output of the program will be
Before swap: x = 10, y = 20
After swap: x = 10, y = 20
We can fake pass-by-reference semantics by passing pointer values:
#include <stdio.h>
void swap( int *a, int *b )
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int main( void )
{
int x = 10, y = 10;
printf( "Before swap: x = %d, y = %d\n" );
swap( &x, &y );
printf( " After swap: x = %d, y = %d\n" );
return 0;
}
Instead of passing the values of x and y, we're passing the results of the expressions &x and &y, which evaluate to the addresses of x and y.
a and b are still separate objects in memory from x and y, but instead of receiving the values of x (10) and y (20), they receive the addresses of x and y.
The expressions *a and *b can kinda-sorta be thought of as aliases for x and y, such that writing a new value to *a is the same as writing a new value to x and writing a new value to *b is the same as writing a new value to y. But this is not true pass-by-reference - it's passing pointers by value and manually dereferencing the pointers. The output of this program will be
Before swap: x = 10, y = 20
After swap: x = 20, y = 10
Switching to C++:
#include <iostream>
void swap( int &a, int &b )
{
int tmp = a;
a = b;
b = tmp;
}
int main( void )
{
int x = 10, y = 20;
std::cout << "Before swap: x = " << x << ", y = " << y << std::endl;
swap( x, y );
std::cout << " After swap: x = " << x << ", y = " << y << std::endl;
return 0;
}
The parameter declarations int &a and int &b declare a and b as references - a and b are not separate objects in memory, but rather they are alternate names or aliases for the objects designated by x and y1. You do not need to explicitly dereference a or b in the C++ code the way you have to in the C code. Like the second C example, the output will be:
Before swap: x = 10, y = 20
After swap: x = 20, y = 10
Logically speaking, anyway - the compiler may be using pointers under the hood to accomplish this, but that's hidden from you. As far as the behavior of the code is concerned, a and b are not independent objects from x and y.

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
}

Is the restrict keyword meaningless on parameters of unique pointer types?

I've noticed a heavy use of the restrict keyword in one of our legacy projects.
I understand the rationale for restrict, but I question its useful-ness when applied to some of these functions.
Take the following two examples:
void funcA(int *restrict i){
// ...
}
void funcB(int *restrict i, float *restrict f){
// ...
}
int main(){
int i = 1;
float f = 3.14;
funcA(&i);
funcB(&i,&f);
}
Is there any valid reason one might tag the parameters of funcA and funcB with restrict?
funcA only takes 1 parameter. How could it have the same address as anything else?
funcB takes parameters of different types. If they were the same address, wouldn't that already be breaking the strict aliasing rule?
restrict is not meaningless with single-pointer-parameter functions.
The restrict keyword is a declaration of intent, for improved optimization. It means that the objects pointed to by the given pointers will not be pointed to by anything else for the life of the (in this case) function parameters.
You don't show the code of the functions, so there may be static variables held inside. Restrict is a guarantee that those static variable don't alias the parameters.
There may be global variables not shown in your example. Restrict is a guarantee that those global variables don't alias the parameters.
In reality, you're right: it's likely that someone just went a little crazy with restrict. But restrict does not mean "this parameter and that parameter". It means "this pointer and any other pointer".
Given the function:
int foo(int *restrict p)
{
*p = 3;
bar();
return *p;
}
a compiler can rather easily see--because of the restrict qualifier--that there is no legitimate way by which bar() can access *p. It can thus optimize the above code into:
int foo(int *restrict p)
{
bar();
*p = 3;
return 3;
}
and it can perform such optimization without having to know anything whasoever about bar(). In the absence of the qualifier, the compiler would have to allow for the possibility that the caller might have e.g. passed the address of a global int which is modified by bar(), but because of restrict a compiler wouldn't have to worry about that.

Does strict aliasing apply when using pointers to struct members?

Does test_func the following snippet trigger undefined behavior under the strict aliasing rules when the two arguments partially overlap?
That is the second argument is a member of the first:
#include <stdio.h>
typedef struct
{
//... Other fields
int x;
//... Other fields
} A;
int test_func(A *a, int *x)
{
a->x = 0;
*x = 1;
return a->x;
}
int main()
{
A a = {0};
printf("%d\n", test_func(&a, &a.x));
return 0;
}
Is the compiler allowed to think test_func will just return 0, based on the assumption that A* and int* will not alias? so the *x cannot overwrite the member?
Strict aliasing refers to when a pointer is converted to another pointer type, after which the contents are accessed. Strict aliasing means that the involved pointed-at types must be compatible. That does not apply here.
There is however the term pointer aliasing, meaning that two pointers can refer to the same memory. The compiler is not allowed to assume that this is the case here. If it wants to do optimizations like those you describe, it would perhaps have to add machine code that compares the pointers with each other, to determine if they are the same or not. Which in itself would make the function slightly slower.
To help the compiler optimize such code, you can declare the pointers as restrict, which tells the compiler that the programmer guarantees that the pointers are not pointing at the same memory.
Your function compiled with gcc -O3 results in this machine code:
0x00402D09 mov $0x1,%edx
Which basically means that the whole function was replaced (inlined) with "set a.x to 1".
But if I rewrite your function as
int test_func(A* restrict a, int* restrict x)
{
a->x = 0;
*x = 1;
return a->x;
}
and compile with gcc -O3, it does return 0. Because I have now told the compiler that a->X and x do not point at the same memory, so it can assume that *x = 1; does not affect the result and skip the line *x = 1; or sequence it before the line a->x = 0;.
The optimized machine code of the restrict version actually skips the whole function call, since it knows that the value is already 0 as per your initialization.
This is of course a bug, but the programmer is to blame for it, for careless use of restrict.
This is not a violation of strict aliasing. The strict aliasing rule says (simplified) that you can access the value of an object only using an lvalue expression of a compatible type. In this case, the object you're accessing is the member x of main's a variable. This member has type int. And the expression you use to access it (*x) also has type int. So there's no problem.
You may be confusing strict aliasing with restrict. If you had used the restrict keyword in the declaration of one of the pointer parameters, the code would be invalid because restrict prevents you from using different pointers to access the same object - but this is a different issue than strict aliasing.

Is it legal to alias "const restrict" pointer arguments?

If dot_product is declared as
float dot_product(const float* restrict a, const float* restrict b, unsigned n);
would calling it with
dot_product(x, x, x_len)
be "undefined", according to the C99 standard?
Edit
x is a pointer, of course, pointing to sizeof(float) * x_len bytes of memory, x_len is unsigned. This question is about aliasing.
I do not have the original C99 (that is, ISO9899:1999) text; I only have a copy of ISO9899:2007:TC3. I expect this text, taken from page 111 of that document, is very similar to the text in the C99 standard.
6.7.3.1 Formal definition of restrict
...
10. EXAMPLE 3
The function parameter declarations
void h(int n, int * restrict p, int * restrict q, int * restrict r)
{
int i;
for (i = 0; i < n; i++)
p[i] = q[i] + r[i];
}
illustrate how an unmodified object can be aliased through two restricted
pointers. In particular, if a and b are disjoint arrays, a call of the form
h(100, a, b, b) has defined behavior, because array b is not modified within
function h.
This seems to clearly call out functions of the form you asked about as having defined behavior, provided the aliased pointers are used for read-only access. Writing through either of the aliased pointers would invoke undefined behavior.
First I don't think that the call itself is UB, UB can only occur inside the function, if the pointers that are passed as parameters are used in a way that conflicts with the specification of restrict. (UB makes not much sense for the call, if that (w/sh)ould be forbidden, this should have been made a constraint violation and not UB.)
Then, UB related to restrict can only appear, if the pointed to object is modified "in any way". So as long as your vectors aren't modified, everything is fine. Inside your function this shouldn't happen, because of the const qualification. And if something outside (say a different thread or a signal handler) modifies your vectors, you are screwed, anyhow.
Yes. It will invoke undefined behavior.
If the restrict keyword is used and the function is declared as :
float dot_product(const float* restrict a, const float* restrict b, unsigned n);
then the compiler is allowed to assume that a and b point to different locations and updating one pointer will not affect the other pointers. The programmer, not the compiler, is responsible for ensuring that the pointers do not point to identical locations.
Since your function call is
dot_product(x, x, x_len)
which is passing the same pointer x to the function, updating any of a or b will affect other causing undefined behavior.

Resources