Link to home
Start Free TrialLog in
Avatar of Sasha_Mapa
Sasha_Mapa

asked on

Strange gcc warning message

Hello.

When compiling the following, gcc gives this warning:
test.c:7: warning: passing arg 1 of `foo' from incompatible pointer type

The code is:

void foo(const int b[8][8]){

}

int main(){
  int b[8][8];
  foo(b);
  return 0;
}

Any ideas why?
Please don't post answers - only comments. I will accept a correct comment as answer.

Alexander Maryanovsky.
Avatar of Paul Maker
Paul Maker
Flag of United Kingdom of Great Britain and Northern Ireland image

try removing the const, do you still get it?
Avatar of Sasha_Mapa
Sasha_Mapa

ASKER

Of course not...

Alexander Maryanovsky.
well then, its because a const pointer is not campatable with a non-const pointer :)
int main(){
 int b[8][8];
 foo((const int **) b);
 return 0;
Well, first of all it doesn't answer my question, and second of all, gcc still gives me the exact same warning with that explicit cast.

Alexander Maryanovsky.
How about:

void foo(const int b[8][8]){

}

int main(){
 const int b[8][8] = { /* ARRAY DATA */ };
 foo(b);
 return 0;
}
This compiles with no warnings under gcc

void foo(const int b[8][8]){
  int i,j;

  for ( i=0 ; i<8 ; i++ ) {
    for ( j=0 ; j<8 ; j++ )
      printf("%d,",b[i][j]);
    printf("\n");
  }
}

int main(){
  int b[8][8];
  int i,j;

  for ( i=0 ; i<8 ; i++ )
    for ( j=0 ; j<8 ; j++ )
      b[i][j] = i*10+j;

  foo((const int (*)[])b);
  return 0;
}

Just to make sure the data was passed correctly to foo(), the output is:
0,1,2,3,4,5,6,7,
10,11,12,13,14,15,16,17,
20,21,22,23,24,25,26,27,
30,31,32,33,34,35,36,37,
40,41,42,43,44,45,46,47,
50,51,52,53,54,55,56,57,
60,61,62,63,64,65,66,67,
70,71,72,73,74,75,76,77,
pkwan: I need a non-const array in the calling method.

zebada: I don't want to do the horrible explicit cast, why should I? My question is not how to "fix" the problem, but why doesn't gcc like it? It looks like perfectly valid code to me...

Alexander Maryanovsky.
It may look like perfectly valid code but it is not.

The compiler will make certain assumptions (usually to do with optimising the code) based on the const modifier.

In the foo() function the compiler is legally allowed to assume that the values in the array will NOT change during the execution of the function. That may enable it to do some tricky optimisations that otherwise would not be "safe".

It is very easy to trick a compiler into letting you change the contents of a const array using totally unrelated pointers to the data in the array. To prevent this and to try to honour the const modifier some compilers (gcc included, I believe) will place const data and things like string literals into read only memory. The read-only is enforced by the operating system. Try to write to read-only memory usually causes a SIGBUS core dump.

That means the compiler requires you to only ever pass const arrays to the foo() function.

If you want/need to move outside of the program's own declarations the compiler warns you that you are not providing a const array where the program has declared one is required. It is telling you that the read-only requirment may not be enforced correctly, and that you cannot rely on the contents of the array that you passed to foo() being the same as when you called foo().

If you *really* need to do that then the compiler provides the option of casting. That means you are overriding the type declaration and telling the compiler that you understand the risks involved but you KNOW that the array data will not change during the execution of the foo function.

Paul
I'm sorry, this seems to contradict what I know and a test I just ran.

What I know - "const" is not for the compiler, it's for the developer. Marking an argument as const simply prevents you from making a mistake and later modifying what you've marked as const.

A test - I added a for loop into the foo(const int b[8][8]) function which printed the array and compiled with -03. I then removed the const modifier and compiled again. The two resulting binaries were identical. If the compiler was using my "const" hint to do something special, it would've produced a different binary - no?

Alexander Maryanovsky.
The platform on which you are running may not support "read-only" memory.

By the way
const IS DEFINITELY for the compiler.
Comments are for the developer.

I hope the following code examples make the use of const a little clearer.
 
This code with the const modifier on line 3:
#include <stdio.h>

const int i=5;

void foo(const int *i){
  *i = 10;
}

int main(){
  printf("Before %d\n",i);
  foo((const int *) &i);
  printf("After %d\n",i);
  return 0;
}

Here's the compile with warnings and the BUS-ERROR.
And the executable checksum:

uranus:/opt/pb> gcc -o t t.c
t.c: In function `foo':
t.c:6: warning: assignment of read-only location
uranus:/opt/pb> ./t
Before 5
Bus error(coredump)
uranus:/opt/pb> sum t
64714 120 t

Now take this code without the const modifier on line 3:
#include <stdio.h>

int i=5;

void foo(const int *i){
  *i = 10;
}

int main(){
  printf("Before %d\n",i);
  foo((const int *) &i);
  printf("After %d\n",i);
  return 0;
}

Here's the compile with the warnings - no BUS-ERROR.
And the executable checksum is different.
uranus:/opt/pb> gcc -o t t.c
t.c: In function `foo':
t.c:6: warning: assignment of read-only location
uranus:/opt/pb> ./t
Before 5
After 10
uranus:/opt/pb> sum t
774 120 t
uranus:/opt/pb>

P.S. I don't place too much faith in the fact that the binaries have different checksums. I can compile the same source code and still get different checksums on the binaries. HP-UX probably sticks a date/time stamp into the binaries.

However you can see that by adding the const modifier to the global integer declaration the program failed with a SIGBUS error because it tried to write to read-only memory.

Regards
Paul

AFAIK, if variables are defined as 'const' they 'can' be paced in the 'code' part of a program. Normal variables are put in the 'data'part. At run-time, the 'code' part is read only, since it can be shared amongst other users. (data can't).

'const' is definitly used by the compiler an linker.

the compiler uses it for optimisations.

suppose you have something like
void foo(const int b[8][8]){
int a;
a=b[1][1];
foo_bis(b);
a=b[1][1];
}

the compiler will know that b is defined 'const', a is a local variable, so a cannot be changed due to the call of foo_bis, and b cannot be changed due to be 'const'.

the pseudo-assembly code lookss like
  load b[1][1]
  store a
  load b
  call foo_bis
  return
This means that inside the exe, the code a=b[1][1] will be in it only once.
If b was not declared const, b could change within foo_bis, and the code a=b[1][1] must be generated twice.
the pseudo-assembly code lookss like
  load b[1][1]
  store a
  load b
  call foo_bis
  load b[1][1]
  store a
  return

hope this helps
try

void foo(const int b[][8]){
}
So since the compiler notices that b is not defined as 'const' it generates a warning telling you that the behavior of the program can be different than the one you expected.

based on the above write a foo_bis that modifies the value b[1][1], and do a printf after each a=b[1][1] assigment.

you'll see what happens....
zebada, your 1st example coredumps because the actual memory you're trying to modify was reserved as const (read only). Your 2nd example demonstrates exactly why my code is fine! In my piece of code, the array is also defined non-const, just like the i in your 2nd example.

What you are saying is that I'm lying to the compiler by passing a read-write piece of memory to a function that takes a read-only piece of memory. This isn't lying - the compiler can still do all the read-only optimizations within the function, because *for that function* the memory is read only.

Alexander Maryanovsky.
have a look my explanation

if you have
void foo(const int b[8][8]){
int a;
a=b[1][1];
foo_bis(a, b);
a=b[1][1];
printf ("%d",a);
}

and modify the content of b in the function foo_bis,
(b must be defined as normal int b[8][8] in main),
the value printed in 'a' should be the first one (and  not the second one).

When compiling without optimisatie the second value 'van' be passed.
> and modify the content of b in the function foo_bis,

If foo_bis takes a regular (non const) int b[8][8] parameter, the code will not compile because you're not allowed to cast a const pointer to a non-const pointer (but vice versa is perfectly ok).

Alexander Maryanovsky.
>zebada, your 1st example coredumps because
>the actual memory you're trying to modify
>was reserved as const (read only).

That was my point exactly.

>What you are saying is that I'm lying to
>the compiler by passing a read-write piece
>of memory to a function
>that takes a read-only piece of memory.

Not lying - overriding the limited understanding the compiler has of the *logic* of your program. There are things that a compiler cannot know that only a programmer can know. You are just "explaining" to the compiler that its OK to do what you want and that you as a programmer are aware of the risks involved. In your case there is no risk involved - you *know* that foo() does not change the value of the const data.

>This isn't lying - the compiler can still
>do all the read-only
>optimizations within the function,
> because *for that function* the memory is read only.

Exactly, but here's the point that I was making that you didn't seem to understand:

By declaring a parameter as const you are telling the compiler that the value CANNOT change. This is a very different thing from telling the compiler that the FUNCTION WILL NOT change the value.

The compiler CANNOT enforce read-only memory. The operating system must do that. To allow the perating system to enforce read-only memory the data declarations must be compiled/linked into the read-only part of the executable's memory - as elfie said: the text segment.

Here's another example:
Should the output of the following code be: a=5 or a=50?

As a programmer you *know* the output should be a=50, but the compiler/optimiser will not know that problem() is going to change the value of *i. In fact the optimiser has been explicitly told that the value of *i will not change and may remove the second assignment statement.

#include <stdio.h>

int i=5;

void problem() {
  i = 50;
}

void foo(const int *i) {
  int a;
  a = *i;
  problem();
  a = *i;    // this line may be optimised out
  printf("a=%d\n",a);
}

int main(){
  foo((const int *) &i);
  return 0;
}
void foo(const int b[8][8]){}
int main(){const int b[8][8];foo(b);return 0;}

compiles without warnings, that's correct according C-syntax (K&R, ANSI-C, and comments above)
But it is not what you want, which doesn't matter 'cause the standards for C *had been* defined long ago :-)

Anyway, you might be right that the const in the foo definition is for "programmer's comment", 'cause foo then should not be able to write to the data nevertheless it was called with a const arg or not. And so the compiler warns you (as explained sevaral times above).

Unfortunatelly, compiler implementations differ on the behaviour of such things, IMHO mainly 'cause the optimizer does not put such const variables into the code segment. If these variables are in the code segment, *and* are modified, you'll get a Segmentation fault. If the underlaying OS does not recognize this invalid write, you most likely get a Bus Error, if the code in question will be executed (which might not occour, for several reasons).

If you change a const variable (in foo), it depends if it works (the change) in foo only, or in foo and main, or if you get a segmentation fault. I know of each type of compilers.
Again, this behaviour is independent from the type of parameter (const or not) you pass to foo.

I'm not shure about gcc, but I assume that it behaves wrong here.
So, if you want be shure that your code worjs right, use the
  -pedantic-errors option.

ahoffmann, I didn't understand your comment at all... You'll have to rephrase it if you want me to understand it :-)

Alexander Maryanovsky.
zebada, so you're basically saying that there's const correctness facility in C? The const is only to hint the compiler that it can do certain optimizations? How does that work in C++, which *does* have const correctness and where casting a normal pointer into a const pointer is valid?

How do you explain that this compiles without any warnings?

void foo(int * a){}

int main(){
  int a = 5;
  int * b = &a;
  foo(b);
  return 0;
}

Wouldn't your arguments which say that the warning is ok apply to this case too?

Alexander Maryanovsky.
I'm sorry Alexander, but I don't understand the point you are making with the code that you supplied.

>so you're basically saying that there's
>const correctness facility in C

Well I'm not 100% sure of the compiler's intention - it may not be that the compiler is enforcing const correction in as much as it is optimising the const declarations into read-only memory. As a side effect of this the read-only restriction is enforced. I don't know enough about C++ to comment on that part of the discussion. In fact you are testing the limits of my C knowledge here as well. Based on this entire discussion and what ahoffmann says above I am rethinking my understanding of this const issue. Some interesting points have been raised.


Oops, it's supposed to be:

void foo(const int * a){}

int main(){
 int a = 5;
 int * b = &a;
 foo(b);
 return 0;
}

Forgot the const in foo().

Alexander Maryanovsky.
And I also meant:
"so you're basically saying that there's *no* const correctness facility in C?"

Alexander Maryanovsky.
I see your point, but have no answer.

Maybe it all comes down to the compiler vendor's implementation of the const keyword.

What does the Ansi 'C' standard say about const function parameters?

Hi,
I think this solution is correct.
You just type cast when you call the function.
void foo(const int b[8][8]){

}

int main(){
 int b[8][8];
 foo((const int)b);
 return 0;
}


thanks

tapas
Hi,
I think this solution is correct.
You just type cast when you call the function.
void foo(const int b[8][8]){

}

int main(){
int b[8][8];
foo((const int)b);
return 0;
}


thanks

tapas
void foo(const int b[8][8]){}
int main(){const int b[8][8];foo(b);return 0;}

compiles without warnings, that's correct according C-syntax (K&R, ANSI-C, and comments above).
If you omit the const in main's declaration of b, you get the warning. This is 100% perfect implementation of gcc.

According your mind that const in the foo declaration is just a "hint for programmer" rather than for the compiler, you're close to the standards, 'cause the standard simply tells that foo **MUST** not change the conntent of b, nevertheless you called foo with a const b or just a (writable) b.

About the rest please reread my comment, then point out the phrases you don't understand.

According, the modification to the code in previous (6..7) comments with "const int *b", keep in mind that this is completetely different to you initial question.
tapasmondal, I specifically asked not to post answers. Also, your "answer" doesn't answer my question AND is wrong (you can't cast int[8][8] into const int.
>What does the Ansi 'C' standard say about const function parameters?

I don't know... I was hoping someone here could explain it for me. Is the complete version available somewhere online? This is all the K&R book says about const:
 
  The const and volatile properties are new with the ANSI standard. The purpose of const is to announce objects that may be placed in read-only memory, and perhaps to increase opportunities for optimization. Except that it should diagnose explicit attempts to change const objects, a compiler may ignore these qualifiers.

ahoffmann:

> Anyway, you might be right that the const in the foo
> definition is for "programmer's comment", 'cause
> foo then should not be able to write to the data
> nevertheless it was called with a const arg or not.
> And so the compiler warns you (as explained sevaral
> times above).

So if I'm right and int[][] can be freely cast into const int[][], why does the compiler warn me? What does it warn me about? What can happen that I did not expect? Why does my last example compile without a warning although it conceptually does the exact same thing as the original example?


Alexander Maryanovsky.


ASKER CERTIFIED SOLUTION
Avatar of zebada
zebada

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hmm, where is this taken from?

Alexander Maryanovsky.
Hi sasha,

I am sorry, for my mistake. I think I am right. I check this code. But it has no warning. U just write the above code and compile.


tapas
tapasmondal:

Here's the result of compiling your code:

test.c: In function `main':
test.c:7: warning: passing arg 1 of `foo' makes pointer from integer without a cast

And even if it compiled - it does *not* answer my question which was not "how do I get rid of this warning?" but "why do I get this warning?".


Alexander Maryanovsky.
tapasmondal:

Here's the result of compiling your code:

test.c: In function `main':
test.c:7: warning: passing arg 1 of `foo' makes pointer from integer without a cast

And even if it compiled - it does *not* answer my question which was not "how do I get rid of this warning?" but "why do I get this warning?".


Alexander Maryanovsky.
Thanks - *that* was the answer I was looking for!

Alexander Maryanovsky.
Sorry it took so long - still don't know why though :(

I am still wondering what the ramifications would be if the compiler did apply the const qualifier at all levels of indirection. Would things screw up? Maybe I'll post a question :)
Here is a appropriate solution which will compile to no error or warning.only u need to do is,call the function as follows:

foo((const int (*)[8])b);
tapas
>I am still wondering what the ramifications would
>be if the compiler did apply the const qualifier at
>all levels of indirection.

I don't think that's what the "answer" is talking about. It says that the fact that implicit casting of a pointer to a const pointer is an exception made specifically for that. That exception, however, was only made to the first level of pointers, so it's ok to implicitly cast (int *) to (const int *), but the exception doesn't apply to (int **), which is why one must cast explicitly.


Alexander Maryanovsky.
What I meant was, what would be the ramification if the exception was extended to all levels of indirection.
i.e. if the exception didn't stop at the top level.

Like you said earlier there is nothing wrong with the code you wrote - there is no ambiguity - there is no confusion so why not let the compiler automatically do the cast for as many nested levels of indirection as necessary.

If there is a reason I would be interested to know it that's all.
Perhaps the answer is somewhere here: http://www.lysator.liu.se/c/rat/title.html :-)

Alexander Maryanovsky.