Reputation: 4020
There are countless questions about pointers here on SO, and countless resources on the internet, but I still haven't been able to understand this.
This answer quotes A Tutorial on Pointers and Arrays in C: Chapter 3 - Pointers and Strings:
int puts(const char *s);
For the moment, ignore the const. The parameter passed to
puts()
is a pointer, that is the value of a pointer (since all parameters in C are passed by value), and the value of a pointer is the address to which it points, or, simply, an address. Thus when we writeputs(strA);
as we have seen, we are passing the address ofstrA[0]
.
I don't understand this, at all.
Why does puts()
need a pointer to a string constant? puts()
doesn't modify and return its argument, just writes it to stdout
, and then the string is discarded.
Ignoring the why, how is it that puts()
's prototype, which explicity takes a pointer to a string constant, accepts a string literal, not a pointer to one? That is, why does puts("hello world");
work when puts()
's prototype would indicate that puts()
needs something more like char hello[] = "hello world"; puts(&hello);
?
If you give, for instance, printf()
a pointer to a string constant, which is apparently what it wants, GCC will complain and your program will segfault, because:
error: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘char (*)[6]’
But giving printf()
a string constant, not a pointer to a string, works fine.
This Programmers.SE question's answers make a lot of sense to me.
Going off that question's answers, pointers are just numbers which represent a position in memory. Numbers for memory addresses are unsigned ints, and C is written in (native) C and assembly, so pointers are simply architecture-defined uint
s.
But this is not the case, since the compiler is very clear in its errors about how int
, int *
and int **
are not the same. They are a pathway that eventually points to something in memory.
Why do functions that need a pointer accept something which is not a pointer, and reject a pointer?
I'm aware a "string constant" is actually an array of characters but I'm trying to simplify here.
Upvotes: 3
Views: 175
Reputation: 123468
Why does
puts()
need a pointer to a string constant?puts()
doesn't modify and return its argument, just writes it to stdout, and then the string is discarded.
puts
receives a pointer to the first character in a string; it will then "walk" down that string until it sees a 0 terminator. A naive implementation would look something like this:
void puts( const char *ptr )
{
while ( *ptr ) // loop until we see a 0-valued byte
putchar( *ptr++ ); // write the current character, advance the pointer
// to point to the next character in the string.
putchar( '\n' );
}
Ignoring the why, how is it that
puts()
's prototype, which explicity takes a pointer to a string constant, accepts a string literal, not a pointer to one? That is, why doesputs("hello world");
work whenputs()
's prototype would indicate thatputs()
needs something more likechar hello[] = "hello world"; puts(&hello);
?
Except when it is the operand of the sizeof
or unary &
operator, or is a string literal being used to initialize another 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.
String literals are stored as arrays of char
(const char
in C++); thus, the string literal "hello world"
is an expression of type "12-element array of char
". When you call puts( "hello world" );
, the string literal is not the operand of the sizeof
or unary &
operators, so the type of the expression is converted to "pointer to char
", and the value of the expression is the address of the first character in the string.
If you give, for instance,
printf()
a pointer to a string constant, which is apparently what it wants, GCC will complain and your program will segfault, because:
error: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘char (*)[6]’
Remember above where I said an array expression is converted to a pointer type except when it is the operand of the sizeof
or unary &
operators or used to initialize another array in a declaration. Assume the declaration
char hello[] = "hello world";
Like above, the expression "hello world"
has type 12-element array of char
; however, because it is being used to initialize another array of char
in a declaration, it is not converted to a pointer expression; instead, the contents of the string literal are copied to the hello
array.
Similarly, if you call printf
as follows:
printf( "%s", &hello );
then the expression hello
is not converted to a pointer to char
; instead, the type of the expression &hello
is "pointer to 12-element array of char
", or char (*)[12]
. Since the %s
conversion specifier expects a char *
, you should just pass the array expression as
printf( "%s", hello );
and with string literals, just use the literal:
printf( "%s", "hello world" );
Going off that question's answers, pointers are just numbers which represent a position in memory. Numbers for memory addresses are unsigned ints, and C is written in (native) C and assembly, so pointers are simply architecture-defined
uint
s.But this is not the case, since the compiler is very clear in its errors about how
int
,int *
andint **
are not the same. They are a pathway that eventually points to something in memory.
C is a (more or less) strongly-typed language; types matter. Even though an int
, int *
, and int **
may take up the same amount of space in memory1, semantically they are very different things and are (usually) not interchangable. A pointer to an int
is a distinct type from a pointer to float
, which is a distinct type from a pointer to an array of char
, etc. This matters for things like pointer arithmetic; when you write
T *p = some_address();
p++;
The expression p++
advances p
to point to the next object of type T
. If sizeof (T)
is 1, then p++
advances a single byte; if sizeof (T)
is 4, then p++
advances 4 bytes (assuming a byte-addressed architecture, which most of us work on).
Upvotes: 2
Reputation: 1385
Why does
puts()
need a pointer to a string constant?
puts()
is defined in such a way so that it can make use of the actual parameter instead of copying it and reusing it. Because it improves performance. Moreover it takes a const
pointer so it can't change the content pointed by the pointer. It is call by reference.
How is it that
puts()
's prototype, which explicitly takes a pointer to a string constant, accepts a string literal, not a pointer to one?
When you pass a string literal, first the string literal is stored in read only memory and then a pointer to that memory is actually passed. So you can call puts()
with any literal like puts("abcd")
, puts("xyz")
. It will work.
error: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘char (*)[6]’
Here your are actually passing a pointer to an array of 6 char
s not a char *
. So the compiler will complain this error.
Upvotes: 1
Reputation: 108938
The expression "hello world"
has type char[12]
.
In most contexts, use of an array is converted to a pointer to its first element: in the case of "hello world"
it is converted to a pointer to the 'h'
, of type char*
.
When using puts("Hello world")
, the array is converted to char*
.
Note that the conversion from array of specific size, loses the size information.
char array[42];
printf("size of array is %d\n", (int)sizeof array);
printf("size of pointer is %d\n", (int)sizeof &array[0]);
Upvotes: 4
Reputation: 5581
An int
is different from int*
because of how it will be used in the code. You can expect to access the memory location that int*
points to and find an integer value. This is called 'strong typing' and the language does this so that there are strict rules for how you use your variables. So even though an int
and int*
might both be the same size, an int
cannot be used as a pointer. Similarly an int**
is a pointer to a pointer, so would have to be dereferenced twice to find the actual integer value it refers to.
In the example of puts(const char*)
the definition of the function tells you that the function expects a memory location (pointer) to a null-terminated set of char
values. When doing the operation, puts
will dereference the location you give it, and print the characters found there. The const
part tells you it won't be changing the values either so that it's safe to send a const
array of char
to it. When you send a literal string like puts("hello")
, the compiler turns that into a pointer to "hello" for you as a convenience, so a pointer is still sent (not a copy of the string).
Regarding your question about printf
, note that char*
and char*[6]
are different. The first indicates a pointer to a null-terminated string, where the second is a pointer to a set of exactly six char
values which may not be null-terminated. The compiler complains because if puts(&hello)
tried to treat the input parameter as a null-terminated string, it would not stop after then length of the array, and would access memory that it should not.
Upvotes: 2
Reputation: 67839
puts()
doesn't need a pointer to a string, it needs a pointer (*
) to a character (char
). It happens that in C, a pointer to a character (char *
) can be assimilated to a string (an array of chars), provided that the end of the string is a null character \0
.
Upvotes: 3
Reputation: 24877
Several questions in there, but hopefully I can illustrate how pointers to pointers work.
The reason puts
need a pointer, is that C really does not have a built in type for a string. A string is just a bunch of char
one after another. Hence, puts
needs a pointer to the first of the chars.
The string literal, "degrades gracefully" to a pointer. This is fancy compiler speak meaning that a string literal actually is a string of chars and is represented by a pointer to the first of the char
s.
You need a pointer to a pointer to a type, for instance, if you want to "return" an array from a function, like so:
bool magic_super_function(int frob, int niz, char** imageptr /* pointer to pointer */)
{
char* img = malloc(frob * niz * IMAGE_DEPTH);
if (NULL == ptr) {
return false;
}
*imageptr = img;
return true;
}
Sometimes an example (even contrived) can illustrate a point. You would call this function like so:
char* img; /* pointer to char */
if (true == magic_super_function(12, 8, &img /* pointer to pointer (to char)*/ )) {
/* Here img is valid and allocated */
/* Do something with img */
} else {
/* img has no valid value here. Do not use it. */
/* Something failed */
}
Upvotes: 2
Reputation: 1244
int **r = 90; r is a double pointer and you are assigning 90 to the pointer. When you dereference, it will try to dereference address 0x90.
Upvotes: 1