Why we need to pass pointer as argument to print a char? - c

I was learning C and i wanna to print a name using function, so i made this code:
#include <stdio.h>
void pn(char x);
void main()
{
pn("HHH");
}
void pn(char x)
{
printf("Hello %s\n",x);
}
and the output is nothing , so i change the argument and make it as pointer and then it works:
void pn(char* x);
void main()
{
pn("HHH");
}
void pn(char* x)
{
printf("Hello %s\n",x);
}
the output is : Hello HHH
as i know, pointer is to store address for a variable , but here i dont send any address ? so why it works only when i put a pointer as argument ?

In the first program the function parameter
void pn(char x);
has the type char that is the function expects as an argument a single character.
However the function is called with the string literal "HHH" as its argument
pn("HHH");
String literals in C have types of character arrays. For example the string literal "HHH" has the type char[4] because the string literal includes also an invisible terminating zero character.
Used in expressions as for example as function arguments arrays are implicitly converted to pointers to their first elements. So the string literal "HHH" is converted to a temporary object of the type char * that points to the first character 'H'..
As a result the function has undefined behavior due to the incompatibility of the parameter and the corresponding argument.
In the second program the function parameter was correctly changed to the type char * and the program produced the expected result.
If you wanted to output just one character in the first program then it should be changed at least the following way
#include <stdio.h>
void pn(char x);
void main()
{
pn("HHH"[0]);
}
void pn(char x)
{
printf("Hello %c\n",x);
}
Or it would be more clear to call the function like
pn( 'H' );
passing a character literal.

A string in C is defined as an array of characters terminated with a null character. Arrays in C are defined by a pointer that points to the first element in the array.
Your function doesn't work because its parameter only accepts a single character as an argument, not a pointer to an array of characters.

"So why it works only when I put a pointer as argument?"
"HHH" is a string literal of type array of 4 char (char [4]).
When you use a string literal as function argument in C, the string literal will be evaluated to a pointer to the first element of the char array consisting the string, type pointer to char (char *).
In this case, "HHH" will gain a pointer to the memory address of the first char element which contains the first H.
If you now use x as parameter of type char, there is:
A type mismatch at calling the function pn between argument and parameter - char * vs. char, which invokes undefined behavior, and
Also a type mismatch at printf("Hello %s\n",x); as the %s conversion specifier expects an argument of type pointer to char. This also invokes undefined behavior since you provided an argument of type char, which is not allowed per the C standard.
"If a conversion specification is invalid, the behavior is undefined. 288) If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined."
"288) See "future library directions" (7.31.11)."
Source: C18, 7.21.6.1/9 - "The fprintf function"
Side notes:
Usually, a compiler should warn you about both type mismatches by default (without any additional flags). So you either seem to ignore the compiler warnings or don't got an appropriate compiler. Switch the compiler to GCC or Clang (if not done yet) and never ignore compiler warnings:
Why should I always enable compiler warnings?

Related

Pointer to int == Pointer to char (somewhat)?

In this code given below , i have declared a pointer to int and we all know that memcpy returns a void pointer to destination string , so if ptr is a pointer to int then why printf("%s",ptr); is totally valid , ptr is not a pointer to char after all.
#include <stdio.h>
#include <string.h>
//Compiler version gcc 6.3.0
int main()
{
char a1[20] ={0} , a2[20] ={0};
int *ptr;
fgets(a1,20,stdin);
fgets(a2,20,stdin);
ptr = memcpy(a1,a2,strlen(a2)-1);
printf("%s \n",ptr);
if(ptr)
printf("%s",a1);
return 0;
}
First consider ptr = memcpy(a1,a2,strlen(a2)-1);. memcpy is declared as void *memcpy(void * restrict, const void * restrict, size_t), so it accepts the a1 and a2 passed to it because pointers to any unqualified object type may be converted to void * or to const void *. (Pointers to object types qualified with const may also converted to const void *.) This follows from the rules for function calls in C 2018 6.5.2.2 7 (arguments are converted to the parameter types as if by assignment) and 6.5.16 1 (one operand is a possibly-qualified void * and the left has all the qualifiers of the right) and 6.5.16 2 (the right operand is converted to the type of the left).
Then memcpy returns a void * that is its first argument (after conversion to void *), and we attempt to assign this to ptr. This satisfies the constraints of the assignment (one of the operands is a void *), so it converts the pointer to the type of ptr, which is int *. This is governed by 6.3.2.3 7:
A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer…
Since a1 is a char array with no alignment requested, it could have any alignment. It might not be suitable for an int. If so, then the C standard does not define the behavior of the program, per the above.
If a1 happens to be suitably aligned for an int or the C implementation successfully converts it anyway, we go on to printf("%s \n",ptr);.
printf is declared as int printf(const char * restrict, ...). For arguments corresponding to ..., there is no parameter type to convert to. Instead, the default argument promotions are performed. These affect integer and float arguments but not pointer arguments. So ptr is passed to printf unchanged, as an int *.
For a %s conversion, the printf rules in 7.21.6.1 8 say “the argument shall be a pointer to the initial element of an array of character type.” While ptr is pointing to the same place in memory as the initial element, it is a pointer to an int, not a pointer to the initial element. Therefore, it is the wrong type of argument.
7.21.6.1 9 says “… If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.” Therefore, the C standard does not define the behavior of this program.
In many C implementations, pointers are simple addresses in memory, int * and char * have the same representation, and the compiler will tolerate passing an int * for a %s conversion. In this case, printf receives the address it is expecting and will print the string in a1. That is why you observed the result you did. The C standard does not require this behavior. Because printf is part of the standard C library, the C standard permits a compiler to treat it specially when it is called with external linkage. The compiler could, hypothetically, treat the argument as having the correct type (even though it does not) and change the printf call into a loop that used ptr as if it were a char *. I am not aware of any compilers that would generate undesired code in this case, but the point is the C standard does not prohibit it.
why printf("%s",ptr); is totally valid
It isn’t - it may work as expected, but it isn’t guaranteed to. By passing an argument of the wrong type to printf, you’ve invoked undefined behavior, which simply means the compiler isn’t required to handle the situation in any particular way. You may get the expected output, you may get garbage output, you may get a runtime error, you may corrupt the state of your system, you may open a black hole to the other side of the universe.

Can we pass pointers in printf?

In the following code, we pass pointer to a string and it works fine but it doesn't works for pointer to an integer. I want to know why ?
#include <stdio.h>
int main(){
char *astring = "afdv";
printf("%s",astring);
int a;
a = 1000;
int *ptr = &a;
printf("\n%d", ptr);
}
In the first printf you did NOT send a pointer. You de-referenced the pointer and hence, you are actually sending a character. Likewise if you want to print an integer, send *ptr, the dereferenced value of the integer pointer.
I'm guessing the source of your confusion to be this-
printf("%s", string_ptr);
perfectly prints the string value instead of the pointer value but
printf("%d", integer_ptr);
prints the pointer value instead of the integer value.
This is because the way printf is implemented. When it sees a %s in the format string, it considers the corresponding parameter as a pointer to a NULL-terminated string. And goes looking for the value of that string at the address contained in the pointer.
But when it sees a %d, it considers the corresponding parameter as an integer value and prints it out directly.
The change in behavior is because types like integer, floats etc are smaller and finite in size and can be passed to printf as values directly. But a string can be arbitrarily large in size. So it makes sense to pass it as a pointer and let printf go find the actual value using the pointer.
Because the language specification says so.
The %s conversion specifier expects its corresponding argument to have type char *; printf will then print out the sequence of characters starting at that address until it sees the string terminator.
The %d conversion specifier expects its corresponding argument to have type int; printf will then print out the text representation of that value as a signed decimal integer.
Check your handy C reference manual for the complete list of conversion specifiers and the types of arguments they expect. If you don't have a handy C reference manual, my preferred one has been C: A Reference Manual since the late 1980s, although the current edition only covers up to C99.
If you use %d and pass something that isn't an integer, then the behavior is undefined. The compiler isn't required to yell at you for passing an argument of the wrong type. If you run the code, anything may happen. In practice, you'll usually get garbled output. If you pass something that isn't a pointer for %s, you may get a runtime error.

How can I test the -> C operator using a struct that allows me to access a struct's field?

I'm trying to test the -> operator, but I can't seem to do that, because when I execute the following program and feed the input with a stream, the program stops working.
Note1: I get a warning before compiling, it says that:
format '%s' expects argument of type 'char *', but argument 2 has type
'int' [-Wformat=]
Note2: if I omit the line printf("%s\n", *(&home1)->name ), it works just fine, and actually prints whatever I wrote.
#include <stdio.h>
#include <string.h>
typedef struct home_type {
char name[30] ;
}home;
int main (void) {
home home1 ;
char name[30] ;
scanf ("%[^\n]%*c", name);
strcpy( home1.name, name) ;
printf("%s\n", home1.name ) ;
printf("%s\n", *(&home1)->name ) ;
return 0 ;
}
Remove * and it works. Your code *(&home1)->name is analogous to *(home1.name) e.g. instead of passing the pointer to the first character, it passes the value of the first char in name; due to default argument promotions, that value is converted into an int.
Thus:
printf("%s\n", (&home1)->name );
should work; however you don't need -> here; -> is now just a shortcut for using a pointer-to-structs more conveniently; i.e. (*ptr).name into a more palatable ptr->name; as home is already a struct and not just a pointer to struct, you should use the . instead.
You just have to drop the * (i.e. not dereference):
printf("%s\n", (&home1)->name );
The member name is an array which gets converted into a pointer (char*) when passing to printf(). However, when you dereference it, it's just a single char that you pass.
Obviously, it doesn't match with what printf() expects for the format %s.
See: What is array decaying?
Operators -> and . are interchangeable:
obj.field is the same as (&obj)->field
ptr->field is the same as (*ptr).field
Note that you have added an asterisk to the result of (&home1)->name, which produces a char. Since printf is a variable-argument function, char is promoted to int during the call, explaining the warning.
When you pass an int for a parameter expecting a pointer, undefined behavior happens; in your case, the program crashes. Removing the dereference operator * will fix this problem.
(&home1)->name is the member array. *(&home1)->name is a dereference on the member array which because of array decay is equivalent to (&home1)->name[0]. This has type char. Passing a char through the ... of a variadic function such as printf promotes it to int (... causes default argument promotions to apply).

&string_name or string_name for %s to print string in C

If I have below string in C:
char s[]="Question";
Then I noted that both of the below prtintf prints the string correctly in terminal.
1.
printf("%s\n",s);
2.
printf("%s\n",&s);
Which is a correct way to print the string in C. If both of them are same, then what is the convention followed ? 1 or 2 ?
Thanks.
char s[]="Question";
printf("%s\n",&s);
is undefined behavior because,
§7.21.6.1/8 The conversion specifiers and their meanings are:
[...]
s If no l
length modifier is present, the argument shall be a pointer to the
initial element of an array of character type.
§7.21.6.1/9 [...] If any argument is not the correct type for the
corresponding conversion specification, the behavior is undefined.
s in this context will decay to a pointer type. Since & yields a pointer, the type you're passing to printf is actually a pointer to a pointer.
The first one is correct, for the second one you should get a warning from your compiler, since it's UB, something like:
[Warning] format '%s' expects argument of type 'char *'

what exactly is a string and an array in c and what is strlen() used for

I have read that string is an array of characters.Array can hold any datatype but string can have only characters,then why is strlen() function working for integer arrays.
An array is a sequence of data item of homogeneous value(same type. A string in C, is an array of characters ending with the NUL-terminator(\0).
The declaration for the strlen function is
size_t strlen(const char *str)
Which means that it takes an argument of type const char*, not an int* or an int[]. So it won't work for integer arrays. You'll get a warning if you try to pass it as an argument to strlen-
warning: passing argument 1 of ‘strlen’ from incompatible pointer type
And then, if you try to execute the program, you'll run into UB(Undefined Behaviour) which means that anything can happen including segmentation faults,crashes,formatting you hard disk etc etc etc

Resources