I am currently working on a project including a somewhat generic linked list implementation using void pointers. Providing some utitily functions for these lists, I decided to make the identifying functions of elements only take (const void *).
After adding the const keyword were necessary, I thought about how correct my code is now (if I implemented everything as it should be before).
As the compiler (GCC) didnt warn me, I decided to take a test.
I compiled the following code with "gcc -g -Wall test.c" and received no warnings whatsover by GCC.
#include <stdio.h>
#include <stdlib.h>
void testf(const void *testp){
*((int32_t *) testp) += 1;
}
void testf2(const int32_t *testp){
*((int32_t *) testp) += 1;
}
int main(){
int32_t testv = 0;
printf("%i \n", testv);
testf(&testv);
printf("%i \n", testv);
testf2(&testv);
printf("%i \n", testv);
return 0;
}
The output is the following:
0
1
2
I did not expect that C would actually crash by this, but I expected to receive a warning by the compiler. In this example im only casting, in my real functions I'm also assigning the const void pointers to a tmp variable.
Is this a bug?
Given how sophisticated todays compilers are, Id atleast expect a warning that Im casting a pointer to a non const pointer. If I change the cast and add the const keyword there too, GCC throws the usual error that I try to assign to a read only location
Am I supposed to rethink my trust to functions declaring const pointers? This is not what I understand as a contract :)
Is this a bug?
No, it's not. By casting you are saying "I know what I am doing" to the compiler.
But GCC does have an option -Wcast-qual which would catch if a qualifier is casted away intentionally.
I compiled the posted code using:
gcc -c -ggdb -Wall -Wextra -pedantic -std=c99 filename.c -o filename.o
(the following list is abbreviated for ease of reading)
error: 'int32_t' undeclared
error: expected expression before ')' token
*((int32_t *) testp) += 1;
warning: unused parameter 'testp'
void testf(const void *testp){
with lots more warnings and errors regarding int32_t
Surely your compiler stated these same items.
BTW: The missing header file is stdint.h.
Related
I'm posting this because I couldn't find a suitable answer elsewhere, not because similar things haven't been asked before.
A project compiles just fine with the following:
#include <stdint.h>
void foo(void)
{ if (bar)
{ static const uint8_t ConstThing = 20;
static uint8_t StaticThing = ConstThing;
//...
}
}
But a cloned project does not, throwing the above error. Looks like we've not completely cloned compiler settings / warning levels etc, but can't find the difference right now.
Using arm-none-eabi-gcc (4.7.3) with -std=gnu99. Compiling for Kinetis.
If anyone knows which settings control cases when this is legal and illegal in the same compiler, I'm all ears. Thanks in advance.
Found the difference.
If optimisation is -O0 it doesn't compile.
If optimisation is -OS it does.
I'm guessing it produces 'what you were asking for, a better way' and fixes it.
Didn't see that coming. Thanks for your input everyone.
Converting some of my comments into an answer.
In standard C, ConstThing is a constant integer, but not an integer constant, and you can only initialize static variables with integer constants. The rules in C++ are different, as befits a different language.
C11 §6.7.9 Initialization ¶4 states:
All the expressions in an initializer for an object that has static or thread storage duration shall be constant expressions or string literals.
§6.4.4.1 Integer constants defines integer constants.
§6.6 Constant expressions defines constant expressions.
…I'm not sure I understand the difference between a 'constant integer' and an 'integer constant'.
Note that ConstThing is not one of the integer constants defined in §6.4.4.1 — so, whatever else it is, it is not an integer constant. Since it is a const-qualified int, it is a constant integer, but that is not the same as an integer constant. Sometimes, the language of the standard is surprising, but it is usually very precise.
The code in the question was compiled by GCC 4.7.3, and apparently compiling with -O0 triggers the error and compiling with -Os (-OS is claimed in the question, but not supported in standard GCC — it requires the optional argument to -O to be a non-negative integer, or s, g or fast) does not. Getting different views on the validity of the code depending on the optimization level is not a comfortable experience — changing the optimization should not change the meaning of the code.
So, the result is compiler dependent — and not required by the C standard. As long as you know that you are limiting portability (in theory, even if not in practice), then that's OK. It's if you don't realize that you're breaking the standard rules and if portability matters, then you have problems of the "Don't Do It" variety.' Personally, I wouldn't risk it — code should compile with or without optimization, and should not depend on a specific optimization flag. It's too fragile otherwise.
Having said that, if it's any consolation, GCC 10.2.0 and Apple clang version 11.0.0 (clang-1100.0.33.17) both accept the code with options
gcc -std=c11 -pedantic-errors -pedantic -Werror -Wall -Wextra -O3 -c const73.c
with any of -O0, -O1, -O2, -O3, -Os, -Og, -Ofast. That surprises me — I don't think it should be accepted in pedantic (strictly) standard-conforming mode (it would be different with -std=gnu11; then extensions are deemed valid). Even adding -Weverything to the clang compilations does not trigger an error. That really does surprise me. The options are intended to diagnose extensions over the standard, but are not completely successful. Note that GCC 4.7.3 is quite old; it was released 2013-04-11. Also, GCC 7.2.0 and v7.3.0 complain about the code under -O0, but not under -Os, -O1, -O2, or -O3 etc, while GCC 8.x.0, 9.x.0 and 10.x.0 do not.
extern int bar;
extern int baz;
extern void foo(void);
#include <stdio.h>
#include <stdint.h>
void foo(void)
{
if (bar)
{
static const uint8_t ConstThing = 20;
static uint8_t StaticThing = ConstThing;
baz = StaticThing++;
}
if (baz)
printf("Got a non-zero baz (%d)\n", baz);
}
However, I suspect that you get away with it because of the limited scope of ConstThing. (See also the comment by dxiv.)
If you use extern const uint8_t ConstThing; (at file scope, or inside the function) with the initializer value omitted, you get the warning that started the question.
extern int bar;
extern int baz;
extern void foo(void);
#include <stdio.h>
#include <stdint.h>
extern const uint8_t ConstThing; // = 20;
void foo(void)
{
if (bar)
{
static uint8_t StaticThing = ConstThing;
baz = StaticThing++;
}
if (baz)
printf("Got a non-zero baz (%d)\n", baz);
}
None of the compilers accepts this at any optimization level.
EDIT: problem explained more in depth here (thank you #Eric Postpischil). It seems to be a bug in GCC.
First, let me start with some context: the code I'm writing is using an API I can't change, on a GCC version I can't change, with compilation flags I'm not allowed to remove, and when I'm done with it must have precisely zero warnings or #pragmas.
EDIT: no unions either.
EDIT2: assume the build system also uses -Wall -ansi -pedantic and every other warnings under the sun.
I'll confirm the GCC version tomorrow but I'm fairly certain it's not above GCC 7. In the meantime I'm testing with GCC 6.3.
EDIT3: I'm marking the issue as 'answered'. For completeness' sake, I'm adding some more information below:
I've checked the compiler version being used, and it's not pretty. We're using Mingw and a gcc.exe --version tells me it's GCC 3.4.5.
Furthermore, compilation flags include wall wextra wcast-qual wpointer-arith wconversion wsign-conversion along with others that are not relevant to the problem at hand.
The problem
Consider the following code:
#include "stdio.h"
#include "stdint.h"
typedef uint32_t MyType[4];
const MyType* foo(const uint8_t* a)
{
return (const MyType*) a;
}
void myapi_foo(const MyType* d) {}
int main()
{
uint8_t a[4*sizeof(uint32_t)];
const MyType* b = foo((const uint8_t*) a);
myapi_foo(b);
return 0;
}
Compiled with GCC and the -Wcast-qual flag, this code will throw the following warning:
warning: cast discards ‘const’ qualifier from pointer target type [-Wcast-qual]
return (const MyType*) a;
EDIT: to clarify, the error is on this line:
return (const MyType*) a;
The cause of the problem
I know the root cause of the problem is the typedef type MyType which is in fact an array. Sadly, I do not have the luxury of modifying this typedef, nor the API function myapi_foo and its dubious choice of parameter type.
To be honest, I don't really understand why is the compiler so unhappy about this cast, so clarifications are more than welcome.
The question
What would be the cleanest way of indicating to the compiler everything should be treated as a pointer to const data?
Discarded and potential solutions
Here are a few 'solutions' that I have found but left me unsatisfied:
Remove the -Wcast-qual flag. I cannot do that due to code quality rules.
Add a #pragma to turn off the warning around that part of the code (as shown here). Similarly I'm not allowed to do that.
Cast the pointer to an integer, then cast back to a pointer (as shown here) return (const MyType*) (uint32_t) a;. It's very crude, but using uint32_t as memory addresses has precedent in this project so I might have to use it as a last ditch effort.
EDIT: #bruno suggested using an union to side-step the problem. This is a portable and fairly elegant solution. However, the aforementioned code quality rules downright bans the use of unions.
EDIT: #Eric Postpischil and #M.M suggested using a (const void*) cast return (const void*) a;, which would work regardless of the value of sizeof(MyType*). Sadly it doesn't work on the target.
Thank you for your time.
This is GCC bug 81631. GCC fails to recognize the cast to const MyType * retains the const qualifier. This may be because, in this “pointer to array of four const uint32_t”, GCC performs a test of whether the array is const whether than of whether the array elements are const.
In some GCC versions, including 8.2, a workaround is to change:
return (const MyType*) a;
to:
return (const void *) a;
A more drastic change that is likely to work in more versions is to use:
return (const MyType *) (uintptr_t) a;
Note About Conversion and Aliasing:
It may be a problem that this code passes a to a function that casts it to const MyType *:
uint8_t a[4*sizeof(uint32_t)];
const MyType* b = foo((const uint8_t*) a);
In many C implementations, MyType, being an array of uint32_t, will require four-byte alignment, but a will only require one-byte alignment. Per C 2018 6.3.2.3 6, if a is not correctly aligned for MyType, the result of the conversion is not defined.
Additionally, this code suggests that the uint_t array a may be used as an array of four uint32_t. That would violate C aliasing rules. The code you show in the question appear to be a sample, not the actual code, so we cannot be sure, but you should consider this.
You can do that :
const MyType* foo(const uint8_t* a)
{
union {
const uint8_t* a;
const MyType* b;
} v;
v.a = a;
return v.b;
}
w.c being your modified file :
pi#raspberrypi:/tmp $ gcc -pedantic -Wall -Wcast-qual w.c
pi#raspberrypi:/tmp $
That works whatever the compiler (no #pragma) or the respective size of int and pointer(no cast between int and pointer), but I am not sure this is very elegant ;-)
It is strange to have that foo function and at the same time compile with Wcast-qual, it's contradictory
Edit, If you cannot use union you can also do that
const MyType* foo(const uint8_t* a)
{
const MyType* r;
memcpy(&r, &a, sizeof(a));
return r;
}
Compilation :
pi#raspberrypi:/tmp $ gcc -pedantic -Wall -Wcast-qual w.c
pi#raspberrypi:/tmp $
If nothing works, you might like to use the uintptr_t hammer, if the implmentation provides it. It is optional by the C11 Standard:
const MyType* foo(const uint8_t* a)
{
uintptr_t uip = (uintptr_t) a;
return (const MyType*) uip;
}
Hitting below warning with new gcc version 6.X
Warning: 'temp' may be used uninitialized in this function [-Wmaybe-uninitialized]
Code:-
int temp;
if (logcat (MSPRO_P->regs[test],
byte, &temp, test) == FALSE){
memErrorMsgHistoryVa (MSPRO_MEMP, "Invalid Data Count 0 value");
MSPRO_P->flashCmdError = TRUE;
}
gcc isn't supposed to warn about passing a pointer to an uninitialized variable to a function it doesn't know anything about (the assumption is that the function will initialize it). So I'm pretty sure that gcc knows things about logcat and the uninitialized use is detected in there. Maybe it got inlined or such.
Example:
$ cat > foo.c
static int
bar(int *a)
{
return *a + *a;
}
int
foo(void)
{
int x;
int y = bar(&x);
return x + y;
}
$ cc -Wall -c foo.c
$
Here, despite it being blindingly obvious to humans, gcc doesn't actually know what happens inside the function bar. So no warning.
Let's help gcc to understand what's going on:
$ cc -O1 -Wall -c foo.c
foo.c: In function ‘foo’:
foo.c:4:12: warning: ‘x’ is used uninitialized in this function [-Wuninitialized]
return *a + *a;
~~~^~~~
foo.c:10:6: note: ‘x’ was declared here
int x;
^
$
Just turning on optimization helped gcc to see what's going on (probably some inlining happened).
From the minimal piece of code you've shown and the warning message, where it looks like you cut out the bit that actually tells you exactly where in your code the problem happens, I conclude that the problem is in your logcat function.
temp is uninitialized after int temp;.
logcat (MSPRO_P->regs[test], byte, &temp, test)
Since a pointer to temp is passed to the function, we, as programmers can guess that this function is supposed to initialize temp. But that is very difficult, if not impossible, for the compiler to assert with absolute certainity, specially when that function is in separate translation unit. From compilers perspective, there is no easy way to tell whether logcat will write to *temp or read it first in uninitialized state. And that's why the warning.
The easiest way to get rid of this warning is assign some initial value to temp, like:
int temp = 0
I have some legacy code which was usually compiled for PowerPC with GCC 3.4.4 .
Now I am porting some code parts which I want to compile with the GCC 4.8.1 from MinGW.
At some point in the code I found this:
// Prototypes
void foo(uint8* pData);
uint8 bar();
// Function
void foo(uint8* pData)
{
(uint8) *(pData++) = bar(); // Original Code - Doesn't work with GCC 4.8.1
*(pData++) = bar(); // Works with GCC 4.8.1
}
If I want to compile the line from the original code with the GCC 4.8.1 I get the lvalue required as left operand of assignment error. If I get rid of the cast operator it works fine. Can someone explain why this is? Isn't that cast just redundant and shouldn't matter anyway? And why is it working with the GCC 3.4.4 ?
The result of the cast operator is not an lvalue (you can think of it as a temporary that has the same value as the original object, but it has a different type -- it's just an unnamed value that you can't change), so you can't assign to it.
Edit: as to why this compiled with GCC 4.3: because that compiler is too permissive. Also, you didn't compile with warnings enabled, I assume. gcc -Wall issues the following diagnostic:
quirk.c: In function ‘main’:
quirk.c:8: warning: target of assignment not really an lvalue;
this will be a hard error in the future
I get error in C(Error- Unused Variable) for variable when I type in following code
int i=10;
but when I do this(break it up into two statements)
int i;
i=10;
The error Goes away
..I am using Xcode(ver-4.1)(Macosx-Lion)..
Is something wrong with xcode....
No nothing is wrong the compiler just warns you that you declared a variable and you are not using it.
It is just a warning not an error.
While nothing is wrong, You must avoid declaring variables that you do not need because they just occupy memory and add to the overhead when they are not needed in the first place.
The compiler isn't wrong, but it is missing an opportunity to print a meaningful error.
Apparently it warns if you declare a variable but never "use" it -- and assigning a vale to it qualifies as using it. The two code snippets are equivalent; the first just happens to make it a bit easier for the compiler to detect the problem.
It could issue a warning for a variable whose value is never read. And I wouldn't be surprised if it did so at a higher optimization level. (The analysis necessary for optimization is also useful for discovering this kind of problem.)
It's simply not possible for a compiler to detect all possible problems of this kind; doing so would be equivalent to solving the Halting Problem. (I think.) Which is why language standards typically don't require warnings like this, and different compilers expend different levels of effort detecting such problems.
(Actually, a compiler probably could detect all unused variable problems, but at the expense of some false positives, i.e., issuing warnings for cases where there isn't really a problem.)
UPDATE, 11 years later:
Using gcc 11.3.0 with -Wall, I get warnings on both:
$ cat a.c
int main() {
int i = 10;
}
$ gcc -Wall -c a.c
a.c: In function ‘main’:
a.c:2:9: warning: unused variable ‘i’ [-Wunused-variable]
2 | int i = 10;
| ^
$ cat b.c
int main() {
int i;
i = 10;
}
$ gcc -Wall -c b.c
b.c: In function ‘main’:
b.c:2:9: warning: variable ‘i’ set but not used [-Wunused-but-set-variable]
2 | int i;
| ^
$
But clang 8.0.1 does not warn on the second program. (XCode probably uses clang.)
The language does not require a warning, but it would certainly make sense to issue one in this case. Tests on godbolt.org indicate that clang issues a warning for the second program starting with version 13.0.0.
(void) i;
You can cast the unused variable to void to suppress the error.