Can that function parameter really be pointer-to-const? - c

In the following C program, functions f, g, and h are essentially identical, however clang-tidy says that parameter p can be pointer-to-const in g and h (but not in f). My understanding is that p can't be pointer-to-const in any of them. Is that a false positive?
struct S { int *p; };
extern void x(struct S *s);
void f(int *p)
{
struct S s;
s.p = p;
x(&s);
}
void g(int *p)
{
struct S s = { .p = p };
x(&s);
}
void h(int *p)
{
struct S s = { p };
x(&s);
}
int main()
{
int a = 0;
f(&a);
g(&a);
h(&a);
}
Output from clang-tidy:
$ clang-tidy --checks=* a.c
Error while trying to load a compilation database:
Could not auto-detect compilation database for file "a.c"
No compilation database found in /home/wolfram or any parent directory
fixed-compilation-database: Error while opening fixed database: No such file or directory
json-compilation-database: Error while opening JSON database: No such file or directory
Running without flags.
2 warnings generated.
/home/wolfram/a.c:14:13: warning: pointer parameter 'p' can be pointer to const [readability-non-const-parameter]
void g(int *p)
^
const
/home/wolfram/a.c:20:13: warning: pointer parameter 'p' can be pointer to const [readability-non-const-parameter]
void h(int *p)
^
const
Tried with clang-tidy versions 10 and 11.
The following may be related: https://bugs.llvm.org/show_bug.cgi?id=41393

The tool is bugged. The intention of this warning is to likely to enforce "const-correctness", something that only matters when the pointer is actually de-referenced inside the function. You don't de-reference the pointers.
But more importantly, the C language requires this for simple assignment of pointers (6.5.1.6.1), emphasis mine:
the left operand has atomic, qualified, or unqualified pointer type, and (considering
the type the left operand would have after lvalue conversion) 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;
Making the pointer parameter const would turn s.p = p; and similar into constraint violations - invalid C. The rules of initialization follow the rules of simple assignment too, so the various functions behave identically.

Related

Why is it when I return a `double` from a `void *` function, it becomes incompatible?

I am making a generic programming library, used to make my life a little bit easier when programming in C. The problem I ran across was that when returning a double from a void * function, it results in an incompatible types error. Why is that? I thought void * was supposed to return any datatype, and not just select ones?
main.c
void * _example_code();
int main(void) {
double res = _example_code();
}
void * _example_code() {
return 2.23;
}
The only things automatically convertible to void * are pointers to object types without qualifiers and null pointer constants, per C 2018 6.5.16.1 1.
double is not automatically convertible to void *.
You could return a pointer to double for a void * function, but you would need a double with lifetime extending beyond the function return, and there may be additional issues trying to implement a “generic” function this way. For one thing, when you return a double * or int * or other pointer type for a void *, the calling routine is not informed what type the pointer was before the conversion to void *. You need to write your program to handle that yourself, by providing some mechanism to inform the caller and have the caller convert the void * to the appropriate type.
There is no implicit conversion to void* from double. Such implicit conversions exist only for object pointer types and null pointer constants.
I wonder what you're expecting that code to do. If there were an implicit conversion, it would most likely take the bits of the double value 2.23 and interpret them as a pointer value of type void* (that's how explicit integer-to-pointer conversions typically work). The result would be garbage. In particular, it would not be a pointer to a memory location containing 2.23.
(The language doesn't say much about the results of integer-to-pointer or pointer-to-integer conversions. There's no guarantee that they just reinterpret the bits; that's just the most common implementation. Avoid converting between integers and pointers until and unless you really need to and know what you're doing. And conversions between pointers and floating-point types are not permitted at all.)
The error message I get for your code is:
c.c: In function ‘main’:
c.c:4:18: error: incompatible types when initializing type ‘double’ using type ‘void *’
4 | double res = _example_code();
| ^~~~~~~~~~~~~
c.c: In function ‘_example_code’:
c.c:8:12: error: incompatible types when returning type ‘double’ but ‘void *’ was expected
8 | return 2.23;
| ^~~~
The error message is actually a bit misleading. It's certainly true that void* and double are incompatible, but compatibility is not required here. For example, int and long are incompatible types, but you can legally return an int value from a function returning long, and the value will be implicitly converted. Type compatibility, as defined by the C standard, is a stronger condition that implicit convertibility. To a first approximation, a type is only compatible with itself (though there are other cases).
I've submitted a gcc bug report for the misleading error message:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107091
clang has a similar issue:
https://github.com/llvm/llvm-project/issues/58066
(Of course the code is still in error.)
C does not have built-in generic containers, void * is a generic data pointer: you can store the value of a pointer to any data type into it and get back the original pointer by converting it back to the original type.
The effect of storing a double into a void * is not defined by the C Standard and your compiler complains that it is an invalid conversion.
For a specified list of types of objects, say int, double and const char *, you can define a generic container this way:
#include <stdio.h>
typedef struct generic {
enum { INT, DOUBLE, STRING } type;
union {
int i;
double d;
const char *s;
} u;
} generic;
generic function1(void);
generic function2(void);
generic function3(void);
int print_generic(const char *s, generic v);
int main() {
generic v1 = function1();
generic v2 = function2();
generic v3 = function3();
print_generic("v1: ", v1);
print_generic("v2: ", v2);
print_generic("v3: ", v3);
return 0;
}
generic function1(void) {
return (generic){ DOUBLE, { .d = 2.23 }};
}
generic function2(void) {
return (generic){ INT, { .i = 42 }};
}
generic function3(void) {
return (generic){ STRING, { .s = "Hello" }};
}
int print_generic(const char *s, generic v) {
switch (+v.type) {
case INT: return printf("%s%d\n", s, v.u.i);
case DOUBLE: return printf("%s%g\n", s, v.u.d);
case STRING: return printf("%s%s\n", s, v.u.s);
default: return printf("%s%s\n", s, "unknown");
}
}
Also note that _example_code is a reserved identifier. Do not start you identifiers with an underscore, these are reserved for the compiler's internal needs.

type-punning a char array struct member

Consider the following code:
typedef struct { char byte; } byte_t;
typedef struct { char bytes[10]; } blob_t;
int f(void) {
blob_t a = {0};
*(byte_t *)a.bytes = (byte_t){10};
return a.bytes[0];
}
Does this give aliasing problems in the return statement? You do have that a.bytes dereferences a type that does not alias the assignment in patch, but on the other hand, the [0] part dereferences a type that does alias.
I can construct a slightly larger example where gcc -O1 -fstrict-aliasing does make the function return 0, and I'd like to know if this is a gcc bug, and if not, what I can do to avoid this problem (in my real-life example, the assignment happens in a separate function so that both functions look really innocent in isolation).
Here is a longer more complete example for testing:
#include <stdio.h>
typedef struct { char byte; } byte_t;
typedef struct { char bytes[10]; } blob_t;
static char *find(char *buf) {
for (int i = 0; i < 1; i++) { if (buf[0] == 0) { return buf; }}
return 0;
}
void patch(char *b) {
*(byte_t *) b = (byte_t) {10};
}
int main(void) {
blob_t a = {0};
char *b = find(a.bytes);
if (b) {
patch(b);
}
printf("%d\n", a.bytes[0]);
}
Building with gcc -O1 -fstrict-aliasing produces 0
The main issue here is that those two structs are not compatible types. And so there can be various problems with alignment and padding.
That issue aside, the standard 6.5/7 only allows for this (the "strict aliasing rule"):
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
a type compatible with the effective type of the object,
...
an aggregate or union type that includes one of the aforementioned types among its members
Looking at *(byte_t *)a.bytes, then a.bytes has the effective type char[10]. Each individual member of that array has in turn the effective type char. You de-reference that with byte_t, which is not a compatible struct type nor does it have a char[10] among its members. It does have char though.
The standard is not exactly clear how to treat an object which effective type is an array. If you read the above part strictly, then your code does indeed violate strict aliasing, because you access a char[10] through a struct which doesn't have a char[10] member. I'd also be a bit concerned about the compiler padding either struct to meet alignment.
Generally, I'd simply advise against doing fishy things like this. If you need type punning, then use a union. And if you wish to use raw binary data, then use uint8_t instead of the potentially signed & non-portable char.
The error is in *(byte_t *)a.bytes = (byte_t){10};. The C spec has a special rule about character types (6.5§7), but that rule only applies when using character type to access any other type, not when using any type to access a character.
According to the Standard, the syntax array[index] is shorthand for *((array)+(index)). Thus, p->array[index] is equivalent to *((p->array) + (index)), which uses the address of p to compute the address of p->array, and then without regard for p's type, adds index (scaled by the size of the array-element type), and then dereferences the resulting pointer to yield an lvalue of the array-element type. Nothing in the wording of the Standard would imply that an access via the resulting lvalue is an access to an lvalue of the underlying structure type. Thus, if the struct member is an array of character type, the constraints of N1570 6.5p7 would allow an lvalue of that form to access storage of any type.
The maintainers of some compilers such as gcc, however, appear to view the laxity of the Standard there as a defect. This can be demonstrated via the code:
struct s1 { char x[10]; };
struct s2 { char x[10]; };
union s1s2 { struct s1 v1; struct s2 v2; } u;
int read_s1_x(struct s1 *p, int i)
{
return p->x[i];
}
void set_s2_x(struct s2 *p, int i, int value)
{
p->x[i] = value;
}
__attribute__((noinline))
int test(void *p, int i)
{
if (read_s1_x(p, 0))
set_s2_x(p, i, 2);
return read_s1_x(p, 0);
}
#include <stdio.h>
int main(void)
{
u.v2.x[0] = 1;
int result = test(&u, 0);
printf("Result = %d / %d", result, u.v2.x[0]);
}
The code abides the constraints in N1570 6.5p7 because it all accesses to any portion of u are performed using lvalues of character type. Nonetheless, the code generated by gcc will not allow for the possibility that the storage accessed by (*(struct s1))->x[0] might also be accessed by (*(struct s2))->x[i] despite the fact that both accesses use lvalues of character type.

Function pointer as a const argument

Is it possible to pass a function pointer as a const argument?
I get the following gcc error:
warning: type defaults to 'int' in declaration of 'f'
[-Wimplicit-int] void execute(void (const *f)(void));
...when compiling this code:
#include <stdio.h>
void print(void);
void execute(void (const *f)(void));
int main(void)
{
execute(print); // sends address of print
return 0;
}
void print(void)
{
printf("const!");
}
void execute(void (const *f)(void)) // receive address of print
{
f();
}
This is not a duplicate of What is meaning of a pointer to a constant function?, which addresses pointers to const functions as opposed to a const function argument.
The syntax you want is:
void execute(void (* const f)(void));
This says that f is a const pointer to a function that takes no arguments ((void) is a special syntax for no arguments) and returns nothing (void). A const on the right side of an * says that the pointer is const. A const on the left side says the pointer points to something that is const.
The fact that it is a const pointer means that you will not change f (assign a new value) in the function, and the compiler should give you an error if you do (unless the change is hidden from it, which it can be by casts). It does not say anything about the function itself—the function pointed to is not const in any sense because the parameter is const.
When you wrote:
void execute(void (const * f)(void));
the const on the left side of the * means that the pointer was pointing to something that was const, rather than that the pointer itself was const. Since it was saying the pointer was pointing to something there and you did not list a specific type, the compiler warned you that the type was missing (and defaulted to int).
The const keyword should be placed between the pointer symbol (*) and the argument name (here, f):
void execute(void (* const f)(void))
{
f();
}
Now if you try to change f's value:
void execute(void (* const f)(void))
{
f = print;
f();
}
...your compiler should output an error similar to this one, as expected:
Error: cannot assign to variable 'f' with const-qualified type

GCC issues warning of incompatible pointer type

When I compile the program below with GCC 4.9.2 I get the following warning: passing argument 1 of ‘P’ from incompatible pointer type. However, I don't see anything wrong with the program. Any clues?
typedef int Row[10];
void P(const Row A[])
{
}
int main(void)
{
Row A[10];
P(A);
return 0;
}
Here is the complete output from GCC to stderr:
test.c: In function ‘main’:
test.c:12:4: warning: passing argument 1 of ‘P’ from incompatible pointer type
P(A);
^
test.c:3:6: note: expected ‘const int (*)[10]’ but argument is of type ‘int (*)[10]’
void P(const Row A[])
^
Edit: The program compiles cleanly with Clang 3.5.0 and the options -pedantic -std=c89 -Wall.
Get rid of the typedef and it should become a little bit clearer:
void P (const int A [][10])
{
}
int main(void)
{
int A[10][10];
P(A);
return 0;
}
The problem is that the array in the function parameter "decays" into a pointer of type const int(*) [10], which is a pointer to an array where the items are const.
This pointer type is not compatible with what you pass from main, because that array decays into an array pointer of type int(*)[10].
There is a rule of "pointer-to-type may be converted to qualified-pointer-to-type". Meaning for example int* may be converted to const int* but not the other way around. But that rule does not apply here.
Because the qualified version of "pointer-to-array" is "const-pointer-to-array", and not "pointer-to-const-array", which is what you have here.
Unfortunately this is a weakness in the C language: you cannot have const correctness while using array pointers. The only solution is a very ugly one:
P( (const int(*)[10]) A);
It might be better to skip const correctness completely for cases like this, in favour of readability.
Edit: In C11 you can do like this, which is more type safe but it still relies on the caller performing a cast:
#define const_array_cast(arr, n) _Generic(arr, int(*)[n] : (const int(*)[n])arr )
void P (const int A [][10])
{
}
int main(void)
{
int A[10][10];
P(const_array_cast(A,10));
return 0;
}

Why does passing arrays of non-const members to functions receiving them as const generate compiler warnings? [duplicate]

The const modifier in C++ before star means that using this pointer the value pointed at cannot be changed, while the pointer itself can be made to point something else. In the below
void justloadme(const int **ptr)
{
*ptr = new int[5];
}
int main()
{
int *ptr = NULL;
justloadme(&ptr);
}
justloadme function should not be allowed to edit the integer values (if any) pointed by the passed param, while it can edit the int* value (since the const is not after the first star), but still why do I get a compiler error in both GCC and VC++?
GCC: error: invalid conversion from int** to const int**
VC++: error C2664: 'justloadme' : cannot convert parameter 1 from 'int **' to 'const int **'. Conversion loses qualifiers
Why does it say that the conversion loses qualifiers? Isn't it gaining the const qualifier? Moreover, isn't it similar to strlen(const char*) where we pass a non-const char*
As most times, the compiler is right and intuition wrong. The problem is that if that particular assignment was allowed you could break const-correctness in your program:
const int constant = 10;
int *modifier = 0;
const int ** const_breaker = &modifier; // [*] this is equivalent to your code
*const_breaker = & constant; // no problem, const_breaker points to
// pointer to a constant integer, but...
// we are actually doing: modifer = &constant!!!
*modifier = 5; // ouch!! we are modifying a constant!!!
The line marked with [*] is the culprit for that violation, and is disallowed for that particular reason. The language allows adding const to the last level but not the first:
int * const * correct = &modifier; // ok, this does not break correctness of the code

Resources