In some legacy code I have to maintain, & operators are put in front of arrays names whenever the arrays are to be passed as (void *) arguments
Here is a simple example :
char val = 42;
char tab[10];
memcpy(&tab, &val, 1);
It compiles with gcc or clang without errors or warnings. It also gives the expected result.
Is this syntax legal ?
Why does this works ?
Notes : I usually use one of the following syntax :
memcpy(tab, &val, 1);
memcpy(&tab[0], &val, 1);
Epilog :
As an additional test, I used a function taking a (char*) argument instead of (void*)
I get the following warning if I try to compile with clang :
warning: incompatible pointer types passing 'char (*)[10]' to parameter of type 'char *' [-Wincompatible-pointer-types]
Edit 1 :
In the original example tab was given with a size of 1 element
I just changed the size to 10 for the sake of generality.
Edit 2 :
As mentionned in the answers, memcpy takes (void*) and not (char*)
memcpy's parameters are of type void*, not char*. Any argument of pointer type (excluding function pointers) is implicitly converted to void*. This is a special-case rule that applies only to void*.
Given the declaration
char tab[1];
either tab or &tab is valid as an argument to memcpy. They evaluate to pointers of different types (char* and char (*)[1]), but both pointing to the same memory location; converting either to void* yields the same value.
For a function that actually requires a char* argument, only tab is valid; &tab is of the wrong type. (For a variadic function like printf or scanf, the compiler may not be able to detect the type mismatch.)
Related
Let's say you have a function taking a string as an argument:
void foo(char *arg);
If we know for certain that the array (not to be confused with string length, thanks chux) will always have a certain size, let's say 8, then we can instead do:
void bar(char (*arg)[8]);
and then call it like this:
char str[8] = "Hello";
bar(&str);
We need to add the & for this to work properly, but the above code will emit a warning if you pass an array of wrong size or type, which is exactly what I want to achieve. But we will obviously need to modify the body a bit. So my question is simply if this wrapper technique would work:
void bar(char (*arg)[8]) {
char *tmp = (char*) arg;
foo(tmp);
}
What I'm trying to achieve here is that warnings should be emitted if called with an array of wrong size. Is the above solution safe? Is it safe to cast pointer to array of char to pointer to char?
I tried it, and it works, and emits no warnings with -Wall -Wextra -pedantic. And as soon as I change the size of str I get:
<source>: In function 'main':
<source>:18:9: warning: passing argument 1 of 'bar' from incompatible pointer type [-Wincompatible-pointer-types]
18 | bar(&str);
| ^~~~
| |
| char (*)[9]
<source>:9:17: note: expected 'char (*)[8]' but argument is of type 'char (*)[9]'
9 | void bar(char (*arg)[8]) {
| ~~~~~~~^~~~~~~
which is exactly what I want. But is it safe, or is it UB? I would like to do this, not only via a wrapper, but also by rewriting the original function, like
void foo(char (*argaux)[8]) {
char *arg = *argaux;
// Copy body of original foo
I know that I can achieve basically the same thing using structs, but I wanted to avoid that.
Runnable code: https://godbolt.org/z/GnaP5ceMr
char *tmp = (char*) arg; is wrong, these are not compatible pointer types. You can fix this easily though:
char *tmp = *arg;
*arg gives a char[8] which then decays into a pointer to its first element. This is safe and well-defined. And yes, pointers have much stronger "typing" in C than pass-by-value, so the compiler will recognize if an array of wrong size is passed.
Please note however that this leads to other problems: you can no longer have const correctness.
See Const correctness for array pointers?
This is not safe:
char *tmp = (char*) arg;
Because you're attempting to convert a char (*)[8] to a char *. While you might get away with it since a pointer to an array will (at least on x86-64) have the same numeric value as a pointer to the first member of an array, the standard doesn't guarantee that it will work. You would first need to dereference the parameter:
char *tmp = *arg;
In theory you should be able to do this:
void foo(char arg[static 8]);
This means that arg must be an array of at least that size.
The description of this syntax is in section 6.7.6.3p7 of the C standard:
A declaration of a parameter as ‘‘array of type’’ shall be adjusted
to ‘‘qualified pointer to type’’, where the type qualifiers (if
any) are those specified within the [ and ] of the array
type derivation. If the keyword static also appears within the
[ and ] of the array type derivation, then for each call
to the function, the value of the corresponding actual argument
shall provide access to the first element of an array with at least as
many elements as specified by the size expression.
However, most implementations don't enforce this restriction and it doesn't prevent you from passing an array larger than expected.
While reviewing my code I realized I had placed an extra & while passing a char array to strcpy and missed the resulting warning; regardless, everything worked as expected. I then reproduced the behavior in this example:
#include <string.h>
#include <stdio.h>
void main() {
char test1[32] = {0};
char test2[32] = {0};
strcpy(test1, "Test 1\n");
strcpy(&test2, "Test 2\n");
printf(test1);
printf(test2);
printf("%i %i\n", test2, &test2);
}
Here I copy a string to the address of test2 and the compiler complains accordingly:
main.c: In function ‘main’:
main.c:9:12: warning: passing argument 1 of ‘strcpy’ from incompatible pointer type [-Wincompatible-pointer-types]
9 | strcpy(&test2, "Test 2\n");
| ^~~~~~
| |
| char (*)[32]
In file included from main.c:1:
/usr/include/string.h:125:39: note: expected ‘char * restrict’ but argument is of type ‘char (*)[32]’
125 | extern char *strcpy (char *__restrict __dest, const char *__restrict __src)
| ~~~~~~~~~~~~~~~~~^~~~~~
However the code is still compiled and the result seems to ignore the second level of indirection. Even when printing the address &test2 it is the same as simply test2.
./a.out
Test 1
Test 2
-1990876288 -1990876288
I must admit this is a part of the C language that completely escapes me. Why is the & operand seemingly ignored when targeting an array?
The first byte of the first element in the array is at the same place as the first byte of the array, because they are the same byte.
Most C implementations use the memory address, or some representation of it, of the first byte of an object as a pointer to the object. The array contains its elements, and there is no padding: The first element of the array starts where the array starts. So the first byte in the first element is the first byte in the array. So they have the same memory address.
There is a rule in C that converting a pointer to an object to a char * produces a pointer to the first byte of an object (C 2018 6.3.2.3 7). So, given an array a, (char *) &a[0] is a pointer to the first byte of the first element, and (char *) &a is a pointer to the first byte of the array. These are the same byte, so (char *) &a[0] == (char *) &a.
However, &a[0] and &a have different types. If you attempt to compare them directly with &a[0] == &a, the compiler should issue a warning that the types do not match.
If you pass &a as an argument to a routine that expects &a[0], it will often work in most modern C implementations because they use plain memory addresses as pointers, so &a is represented with the same bits (a memory address) as &a[0], so the receiving routine gets the value it expected even though you passed a pointer of the wrong type. However, the behavior of your program will not be defined by the C standard, since you have violated the rules. This was more of a problem in older C implementations when memory models were not simple flat address spaces, and different types of pointers may have had different representations.
Your code works in this case because, once the pointer argument (with or without the extra &) gets to the strcpy function, it is interpreted as a simple char* value. Thus, any pointer arithmetic (such as the likely increments) performed in that function will be correct.
However, there are cases where using a pointer-to-char and pointer-to-array-of-char will yield significantly different results. Pointer arithmetic is such a case: if p is a char* variable, then ++p will add the size of a char (i.e. 1) to the address stored in p; however, if q is an array of char* pointers, then ++q will add the size of a pointer to the address in q. And, if r is the address of an array of character strings, then ++r will add the size of the entire array to the address stored in r.
So, it's good that the compiler warns you about that extra &. Be very careful about addressing (no pun intended) such issues, if ever your compiler warns you about them.
Why is the & operand seemingly ignored when targeting an array?
The conversion of char (*)[32] to char * is UB.
Is is not ignored by the compiler, hence the warning.
The compiler emitted code did convert the pointer from one type to the other in a common fashion resulting in acceptable behavior. Still remains UB.
Best if the programmer does not ignore the warning.
I am trying to build the M-SIM architecture simulator, but when I run the make utility, gcc reports this error (it is not even a warning)
note: expected 'char *' but argument is of type 'const char *'
Since when this is considered an error. Is there any flags that can bypass this check?
This is an error because passing a const char* argument to a function that takes a char* parameter violates const-correctness; it would allow you to modify a const object, which would defeat the whole purpose of const.
For example, this C program:
#include <stdio.h>
void func(char *s) {
puts(s);
s[0] = 'J';
}
int main(void) {
const char message[] = "Hello";
func(message);
puts(message);
return 0;
}
produces the following compile-time diagnostics from gcc:
c.c: In function ‘main’:
c.c:10:5: warning: passing argument 1 of ‘func’ discards qualifiers from pointer target type
c.c:3:6: note: expected ‘char *’ but argument is of type ‘const char *’
The final message is marked as a "note" because it refers to the (perfectly legal) declaration of func(), explaining that that's the parameter declaration to which the warning refers.
As far as the C standard is concerned, this is a constraint violation, which means that a compiler could treat it as a fatal error. gcc, by default, just warns about it and does an implicit conversion from const char* to char*.
When I run the program, the output is:
Hello
Jello
which shows that, even though I declared message as const, the function was able to modify it.
Since gcc didn't treat this as a fatal error, there's no need to suppress either of the diagnostic messages. It's entirely possible that the code will work anyway (say, if the function doesn't happen to modify anything). But warnings exist for a reason, and you or the maintainers of the M-SIM architecture simulator should probably take a look at this.
(Passing a string literal to func() wouldn't trigger these diagnostics, since C doesn't treat string literals as const. (It does make the behavior of attempting to modify a string literal undefined.) This is for historical reasons. gcc does have an option, -Wwrite-strings, that causes it to treat string literals as const; this actually violates the C standard, but it can be a useful check.)
As I mentioned in a comment, it would be helpful if you'd show us the code that triggers the diagnostics.
I even downloaded and built the M-SIM architecture simulator myself, but I didn't see that particular message.
Pointers to const-qualified types do not implicitly convert to pointers to non-const-qualified types. An explicit conversion via a cast is necessary, for example:
foo((char *)bar)
First in a function call (of a function defined with a prototype), the arguments are converted to the type of the parameters as if by assignment.
You can assign a value of type char * to an object of type const char * but you cannot assign a const char * value to a char * object.
This constraint appears in the constraints of assignment operator:
(C99, 6.5.16.1p1) "One of the following shall hold: [...] - 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;"
This constraint permits the first assignment but disallows the second.
Declaring a pointer with type const char * means you won't modify the object pointed to by the pointer. So you can assign the pointer a value of char * type, it just means the object won't be modified through the const char * pointer.
But declaring a pointer of type char * means you could modify the object pointed to by the pointer. It would not make sense to assign it a value of const char *.
Remember that in C, const does not mean constant but rather read-only. The const qualifier put before pointer types means you promise not to modify objects through objects of these pointers types.
Take these steps if you haven't already:
Declare a char pointer.
If necessary, allocate space and copy contents from the constant string. (e.g. by
using strdup())
And substitute the constant char pointer with the new char pointer.
Python spoiled me and trying to wrap my mind around C now is being a bloodbath of stupid errors. This is one I can't quite understand.
I wanted the C equivalent of Python's os.path.split, but there's no exact equivalent. strsep looks similar enough, but needs some massaging to be used simply.
First off, I defined my path type: a string of given length.
#define MAX_PATH_LEN 200 /* sigh */
typedef char t_path[MAX_PATH_LEN];
Then I wrote some code that does the actual massaging, attempting to avoid side effects -- just to keep things fool proof.
typedef struct {
t_path next;
t_path remainder;
} t_path_next
t_path_next path_walk_into(t_path path) {
t_path_next output;
t_path my_next, my_remainder = "/";
strncpy(my_next, path, MAX_PATH_LEN);
strsep(&my_next, my_remainder);
output.remainder = my_remainder;
output.next = my_next;
return output;
}
gcc, however, is not impressed.
badp#delta:~/blah$ gcc path.c -Wall
path.c: In function ‘path_walk_into’:
path.c:39: warning: passing argument 1 of ‘strsep’ from incompatible pointer type
/usr/include/string.h:559: note: expected ‘char ** __restrict__’ but argument is of type ‘char (*)[200]’
path.c:41: error: incompatible types when assigning to type ‘t_path’ from type ‘char *’
path.c:42: error: incompatible types when assigning to type ‘t_path’ from type ‘char *’
I am baffled by the note -- how are char ** and char (*)[200] really different -- but the error is even weirder. I want to assign a variable I declared t_path in a field of type t_path, but I don't get to.
Why is that?
For anybody interest here's the correctly working version of the function:
t_path_next path_walk_into(t_path path) {
t_path_next output;
t_path my_path, delim = "/";
char* my_path_ptr = my_path;
strncpy(my_path, path, MAX_PATH_LEN);
strsep(&my_path_ptr, delim); //put a \0 on next slash and advance pointer there.
if (my_path_ptr == NULL) //no more slashes.
output.remainder[0] = 0;
else
strncpy(output.remainder, my_path_ptr, MAX_PATH_LEN);
strncpy(output.next, my_path, MAX_PATH_LEN);
return output;
}
The errors: You can't directly assign to an array, such as a string, in C. You need to copy char by char, or call str(n)cpy, which does it for you.
For the warning : you are probably already aware that array may decay to pointer. That is, for example, what makes an array acceptable as an argument to a function where a pointer is expected. In your case, what you have is a pointer to an array : there is no reason for such a thing to get converted to a pointer to pointer.
For the record, the C99 standard says (6.3.2.1/3) :
Except when it is the operand of the sizeof operator or the unary & operator, or is a
string literal used to initialize an array, an expression that has type ‘‘array of type’’ is
converted to an expression with type ‘‘pointer to type’’ that points to the initial element of
the array object and is not an lvalue.
You're in the context of a unary & : no conversion for you.
For the error : it has already been answered, but array assignment is not directly possible. You might want to use strcpy or strncpy.
Although it runs correctly, the following results in the aforementioned compiler warning:
return ((item - (my->items))/(my->itemSize));
'item' is a 'void *'; 'my->items' is a 'void *'; 'my->itemSize' is an 'int'
Casting 'item' and 'my->items' as an 'int *' caused the program to run improperly. What is the best way to remove the warning?
Additions and subtractions with pointers work with the size of the pointed type:
int* foo = 0x1000;
foo++;
// foo is now 0x1004 because sizeof(int) is 4
Semantically speaking, the size of void should be zero, since it doesn't represent anything. For this reason, pointer arithmetic on void pointers should be illegal.
However, for several reasons, sizeof(void) returns 1, and arithmetic works as if it was a char pointer. Since it's semantically incorrect, you do, however, get a warning.
To suppress the warning, use char pointers.
Cast to a char *:
return ((char *)item - (char *)my->items)/my->itemSize);
Since char is size of 1 byte, you will get the value you are expecting vs your int * pointer example which calculates how many ints are between the two address. That's how pointer arithmetic works.
The problem is that you are doing aritmethical operations in a pointer, I wonder how is that running properly.
If you're trying to do normal arithmetic, you have to dereference the pointer (e.g. *item). If you're trying to do pointer arithmetic, the pointer has to be of a type, like char* or int* (otherwise the compiler won't know how much to increment by).