Different semantics between `char *` and `int *` - c

I want to know why declaring and assigning a char * versus an int * in C have different semantics.
All of the following code is compiled with clang using the flags -Wall -Werror -std=gnu99 --pedantic.
I am trying to understand whether the difference is something that just is, or whether there's some bigger difference between int * and char * that I have yet to realize.
// this will compile
int main(int argc, const char *argv[]) {
char *a;
a = "1";
printf("a: %s\n", a);
return EXIT_SUCCESS;
}
// this will not compile. It's a parallel construction, substituting char for int
#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[]) {
int *a;
a = 1;
printf("a: %d\n", a);
return EXIT_SUCCESS;
}
// but this will compile
#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[]) {
int *a;
int b = 1;
a = &b;
printf("a: %d\n", *a);
return EXIT_SUCCESS;
}

The gist of it:
The data type of "1" is char*. So you are assigning a char* to a char* variable.
The data type of 1 is int. So you are assigning an int to an int* variable which won't work since the types don't match.

The second example won't compile because you have -Werror on, and you're attempting to assign an integer to a pointer without a cast, which is normally just a warning. The same would happen if you did char* a; a='a'; because you'd be attempting to assigning a char to char*.
In terms of different semantics between pointer types - what is stored in the memory as the pointer, it's likely not going to be different, but the pointer type will affect compilation. Mainly when it comes to pointer arithmetic, array indexing, and using pointers to aggregate (struct) types and functions.
Things start acting differently when you start using pointer arithmetic/array indexing. For example:
int value = 4
char *charp = &value;
int* intp = &value;
These will both be valid pointers. You'll also see different behaviors during compilation if you're working with function pointers, which you can use function call syntax with, and struct pointers, where you can access members of the referenced structure with the -> operator.

You're dealing with a difference between array, pointer, and scalar semantics.
Except when it is the operand of the sizeof or unary & operators, or is a string literal used to initialize a character array in a declaration, an expression of type "N-element array of T" will be converted ("decay") to an expression of type "pointer to T", and the value of the expression will be the address of the first element of the array.
The string literal "1" is an array expression of type char [2]. In this context, it "decays" to an expression of type char *, and the value of the expression is the address of the first character of the string. So you're assigning like to like:
a = "l"; // char * = char *
This doesn't happen with the integer variant. The literal 1 has type int, but you're trying to assign that int value to an int * object:
a = 1; // int * = int
which should trigger a diagnostic. You can only assign an int * value to an int * object - you would need to explicitly cast the 1:
a = (int *) 1;
which will suppress the diagnostic, but 1 is very likely not a valid memory address on your platform, and attempting to use it will cause problems.

Related

execv arguments error - "expected char * const* but argument is of type const char *"

I have a function whose argument is const char *array[]
array[0] is the path, the rest are the arguments and it ends with NULL.
However, if I try to do execv(array[0], array) I get expected char * const* but argument is of type const char *
How do I go about this, and what is the difference between char * const* and const char *?
void start(const char *array[]) {
execv(array[0], array);
}
First, the error message is not copied correctly. If I run your code in GCC it shows this message instead (note the final *):
note: expected ‘char * const*’ but argument is of type ‘const char **’
which makes more sense as the message you show in the question, does not match the code you show. There is a mismatch in level or indirection.
That said, let's look at this part:
and what is the difference between char * const* and const char *?
Actually it is
and what is the difference between char * const* and const char **?
The first is a pointer to a const pointer to a char. The char that is pointed to is not const and might in theory be changed by execv.
The latter is a pointer to a pointer to a const char. This means, the char that is pointed to mustn't be modified. It might be some read-only string literal in ROM. If you pass such a pointer to a function that will try to modify it, it will fail in one way or the other. Therefore you are not allowed to pass a "pointer to const" to a function that does not expect it to be const.
That is what the compiler is telling you.
Now, how can you get rid of that warning...
To silence your compiler you could try to use some cast and cheat about real nature of that parameter.
In the end the problem will stay the same. A function trying to modify your read-only memory will not be working properly.
Instead you need to make a copy of your data:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void start(const char *array[]) {
int i = 0;
// determine number of strings (including NULL)
while (array[i++] != NULL) ;
// Create an array able to hold pointers to copys
char *my_array[i];
// Copy strings into non-const memory
i = 0;
do
my_array[i] = array[i] ? strdup(array[i]) : NULL;
while (array[i++] != NULL);
execv(my_array[0], my_array);
// Free the memory for the copied strings
i = 0;
do
free(my_array[i]);
while (array[i++] != NULL);
}
int main(void)
{
const char *argv[] = {"ls", "ls", NULL};
start(argv);
return 0;
}

QSORT function in C

In the following code, once I remove the commented part which compares strings, I am getting a seg 11 fault. I am unable to understand why! Rest of the code is working fine. Any help is appreciated!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int compare_scores_desc(const void* scorea, const void* scoreb){
int a = *(int*)scorea;
int b = *(int*)scoreb;
return a-b;
}
int compare_names(const void* namea, const void* nameb){
char** a = *(char**)namea;
char** b = *(char**)nameb;
return strcmp(*a,*b);
}
int main(int argc, char* argv[]){
int scores[7] = {456,234,65,563,67,19,100};
int i;
qsort(scores,7,sizeof(int),compare_scores_desc);
puts("\nThese are the scores in order : \n");
for(i=0;i<7;i++)
printf("%i\n",scores[i]);
char *names[] = {"Krishna","Rama","Bhishma","Arjuna"};
/*qsort(names,4,sizeof(char*),compare_names);*/
puts("------------------");
puts("The names in order are : \n");
for(i=0;i<4;i++)
printf("%s\n",names[i]);
return 0;
}
In compare_names(), you are inappropriately dereferencing the arguments after the cast. The types for the local variables are type char **, but you are casting the arguments as char ** and dereferencing that results in a char *.
namea and nameb are pointers to the elements of your array names[] declared in main(). That means, their types are actually pointer to char *. When you dereferenced these arguments but assigned them to a char **, you cause the local variable to treat the char * as a char ** (your compiler should have issued a diagnostic warning you about this problem). Now, you take a pointer value that is a char *, and dereference it when you pass it to strcmp(). This causes the program to treat sizeof(char *) bytes of the string as a pointer value for the strcmp() function. Since 4 or 8 (or whatever sizeof(char *) is) bytes consisting of printable characters reinterpreted as a pointer value rarely yields a valid pointer, when strcmp() tries to use those pointers, a segmentation fault occurs.
One possible fix is to not dereference when you initialize your local variables. However, the arguments are const void *, so you can avoid the cast altogether if you declare your local variables to be a pointer to a const type:
int compare_names(const void* namea, const void* nameb){
char* const * a = namea;
char* const * b = nameb;
return strcmp(*a,*b);
}
Note that your implementation of compare_scores_desc() fails if a - b results in signed integer overflow. For example, if a is INT_MAX and b is -1. You should fix your implementation to work for all cases.
int compare_scores_desc(const void* scorea, const void* scoreb){
const int *a = scorea;
const int *b = scoreb;
return (*a > *b) - (*a < *b);
}
The problem is in your string comparison function, and here is probably the minimal way to fix it:
int compare_names(const void* namea, const void* nameb){
char* a = *(char**)namea;
char* b = *(char**)nameb;
return strcmp(a,b);
}
The namea and nameb arguments are pointers into the string vector. You understand this, which is why you used the char ** type.
However, all you have to do in the function is retrieve the char * pointers from that array. These char * pointers are already strings. You do not have to dereference them again; just pass them to strcmp.
Your original code has a constraint violation which requires a diagnostic. That should have tipped you off:
/* originally */
char** a = *(char**)namea; /* error: initialization from incompatible type */
You're dereferencing a char **, which produces char *, but you're storing that in a char ** again and dereferencing again, thereby wrongly treating the character data as a pointer.

Why do I get a warning trying to pass a 'char (*c)[6]' to a function expecting a 'const char (*c)[6]'?

I know about the char ** vs const char ** thing (like described in the c faq) but I can't see any scenario where doing so with a pointer to arrays would lead to some content inside the arrays themselves being actually modified.
My code:
void fun(const char (*p)[6])
{
printf("%s", p[0]);
}
int main(int argc, char *argv[])
{
char a[6] = "hello";
char (*c)[6];
c = &a;
fun(c);
}
gives the below output when compiled with gcc:
test.c:17:9: warning: passing argument 1 of 'fun' from incompatible pointer type
test.c:5:10: note: expected 'const char (*)[6]' but argument is of type 'char (*)[6]'
The question here is somehow related but has no answer so far. Is it just the compiler being paranoïd and the only way to get rid of the warning is to explicitly cast ? Or is there really a chance something can go wrong ?
It is just a quirk of C language specification. For another example, the char ** to const char *const * conversion is also safe from the const-correctness point of view, yet it is prohibited in C.
This quirk of const-correctness rules was "fixed" in C++ language, but C continues to stick to its original specification in this regard.
Const-conversion is covered by section 6.5.16.1 (1) of the standard:
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;
In this case it looks like T is char [6] and the rest of the requirement clearly holds, as can be seen by modifying your example:
int main(int argc, char *argv[])
{
typedef char c6[6];
c6 a = "hello";
const c6 *p = &a;
}
However this is actually not the case! This intersects with 6.7.3 (8):
If the specification of an array type includes any type qualifiers, the element type is so qualified, not the array type.
So const c6 * actually names the type const char (*)[6]; that is, pointer to array[6] of const char, not pointer to const array[6] of char.
Then the LHS points to the type const char[6], the RHS points to the type char[6], which are not compatible types, and the requirements for simple assignment do not hold.
Actually the reasons are quite similar (char ** vs. pointer of arrays).
For what you are trying to do, the following would suffice (and it works):
void fun(const char *p)
{
printf("%s", p);
}
int main(int argc, char *argv[])
{
char a[6] = "hello";
char *c;
c = a;
fun(c);
}
With what you are trying to do, it would be possible to modify the values as follows that defeats the purpose (just an example):
void morefun(const char *p[6])
{
char d;
char *p1 = &d;
p[1] = p1;
*p1 = 'X';
printf("\nThe char is %c\n", *p[1]);
}
int main(int argc, char *argv[])
{
const char *d[6];
morefun(d);
}

const pointer fix to a variable variable

I can't figure out how to tell C that I want a pointer that will not move. It will always point to the same array. That said, the array members are not constant, but the array itself is global and so, it is at a fixed position.
So, when I code this:
#include <stdio.h>
int v[2]={0, 1};
const int *cpv=v;
int main(void)
{
v[1]=2; printf("%d\n", v[1]);
*(cpv+1)=3; printf("%d\n", v[1]);
cpv[1]=4; printf("%d\n", v[1]);
}
And get this errors:
constp.c: In function ‘main’:
constp.c:9: error: assignment of read-only location '*(cpv + 4u)'
constp.c:10: error: assignment of read-only location '*(cpv + 4u)'
I understand that the compiler thinks I need a const int v[2] to use with a const int *iv. How do I get a constant pointer to do the job?
If you see the error message, I'm not even moving the pointer (like pv++). I'm just dereferencing it dislocated some bytes.
If I do this:
int *pv=cpv;
*(pv+1)=5; printf("%d\n", v[1]);
printf("%p == %p !?\n", cpv, pv);
I get this warning, but it works:
constp.c:9: warning: assignment discards qualifiers from pointer target type
pointer# ./constp
5
0x601020 == 0x601020 !?
Thanks,
Beco.
Move the const qualifier:
int *const cpv=v;
Explanation: in the C declaration rules, this is read from right to left starting at the identifier: "cpv is a constant pointer to int". Your version would be read "cpv is a pointer to int constant".
Note that cpv+1 will still get you a pointer to the int after *cpv; making a pointer const only prevents ++, --, += and -= on it.
You are using a pointer to const, not a const pointer.
const int *cpv=v;
should turn into:
int *const cpv=v;
Pointers kind of "read backwards":
const int * p;
In this case, "p" is a (variable) "pointer" to a "const int". Literally, read that backwards, with the '*' meaning "pointer": "Pointer ... to int const".
Curiously, the following is the same thing:
int const * p; // same thing
Reading backwards, it says, "pointer ... to const int" (the same thing).
So, if you want your pointer to be "constant" (not variable), you need to read-backwards and do just that:
int * const p; // p cannot change
Now, "p" is a constant pointer to a non-constant integer. Not to belabor, but reading backwards that's, "const pointer ... to int".
Using the "same thing" example above, we can now have a constant pointer to a constant integer (the following are the same):
const int * const p;
int const * const p;
You should read them backwards, and they both say the same thing, "contant pointer ... to constant int".
int * const cpv=v;
Having said that, why do you need the pointer at all? The compiler will be able to do a better job if you would access the variable v directly. If you go via a pointer, the generated code have to read the pointer from memory each time you access the array.
#include <stdio.h>
int v[2]={0, 1};
//const int * cpv=v; // cpv is a pointer to int const
// int const * cpv=v; // cpv is a pointer to const int == same as above ==
int *const cpv=v; // cpv is a constant pointer to int
// int const *const cpv=v; // cpv is a constant pointer to const int
//const int *const cpv=v; // cpv is a constant pointer to int const == same as above ==
//const int const *const cpv=v; // == same as above ==
int main(void)
{
v[1]=2; printf("%d\n", v[1]);
*(cpv+1)=3; printf("%d\n", v[1]);
cpv[1]=4; printf("%d\n", v[1]);
}

referring to arrays as pointers

I can't seem to understand the difference between the different declarations on an array or a 2d array.
for instance:
void swap(char **a, char **b) {
char *t = *a;
*a = *b;
*b = t;
}
int main(int argc, char **argv) {
char a[] = "asher";
char b[] = "saban";
swap(&a,&b);
}
this code doesn't compile, it outputs:
warning: passing argument 1 of ‘swap’ from incompatible pointer type
test.c:10: note: expected ‘char **’ but argument is of type ‘char (*)[6]’
isn't a a pointer to first cell of char array and &a is a pointer to pointer?
another example is:
char (*c)[3];
char (*d)[3];
swap(c,d);
doesn't compile either.. is char (*c)[3] same as a pointer to char a[] = "ab" ?
However this does compile:
char *c[3];
char *d[3];
swap(c,d);
so i'm totally confused. Why is there a difference? Is there some rules about that issue to prevent me from mistaking all the time?
Thank you all
I think that this is the source of your confusion.
An array variable is a fixed object. It refers to a fixed set of array members. It cannot be changed, although the values of the array members can.
In all expression contexts other than as the argument to unary & (address of) and sizeof an array will decay into a pointer to its first element.
Given:
char a[] = "asher";
The expression a will decay to a pointer to char (char*) and will point to the first character of a.
The expression &a is a pointer to an array of char (char (*)[]). It is a pointer to the complete array rather that a pointer to the first character. It is a different type to a pointer to the first character of the array although it will have the same value as a pointer to the first character of the array.
However, neither of the expressions a and &a are lvalues, they are temporary pointer values.
You cannot swap arrays, you can only swap pointers but to do this you need lvalue pointers whose address you can take.
void swap(char **a, char **b);
int main(int argc, char **argv) {
char a[] = "asher";
char b[] = "saban";
char* pa = a;
char* pb = b;
swap(&pa, &pb);
}

Resources