printf pointer argument type warning? - c

Is there a good way to get rid of the following warning? I know it's a type issue in that I'm passing a unsigned long pointer and not an unsigned long, but does printf somehow support pointers as arguments? The pedantic in me would like to get rid of this warning. If not, how do you deal with printing de-referenced pointer values with printf?
#include <stdio.h>
int main (void) {
unsigned long *test = 1;
printf("%lu\n", (unsigned long*)test);
return 0;
}
warning: format specifies type 'unsigned long' but the argument has type

unsigned long *test = 1;
is not valid C. If you want to have a pointer to an object of value 1, you can do:
unsigned long a = 1;
unsigned long *test = &a;
or using a C99 compound literal:
unsigned long *test = &(unsigned long){1UL};
Now also:
printf("%lu\n", (unsigned long*)test);
is incorrect. You actually want:
printf("%lu\n", *test);
to print the value of the unsigned long object *test.
To print the test pointer value (in an implementation-defined way), you need:
printf("%p\n", (void *) test);

You don't really need to typecast test, the %p will take any pointer (even though the spec says it takes a void*). Using the %p conversion specifier is the safest (and amazingly, most portable) mechanism to print a pointer via printf.
#include <stdio.h>
int main (void) {
unsigned long ul = 1;
unsigned long *test = &ul;
printf("%p\n", test);
return 0;
}
EDIT: Oh yeah, and also be careful trying to use types like int, long, etc. relying on their type size. This is implementation-dependent and can (and does) change on varying platforms and compilers for those platforms. The C standard just says that long has to be at least as large as int. The POSIX standard for printf "length modifiers" don't specify size in bits, but rather by C type. Thus, you can't presume that sizeof(long) == sizeof(void*) (well, they make that assumption in the Linux kernel, but it's married to gcc where that is always true).

Related

If the size of "long" and "int" are the same on a platform - are "long" and "int" different in any way?

If the representation of a long int and a int are the same on a platform, are they strictly the same? Do the types behave any differently on the platform in any way according to the C standard?
Eg. does this always work:
int int_var;
long long_var;
void long_bar(long *l);
void int_bar(int *i);
void foo()
{
long_bar(&int_var); /* Always OK? */
int_bar(&long_var);
}
I guess the same question applies to short and int, if they happen to be the same representation.
The question arose when discussing how to define a int32_t-like typedef for an embedded C89 compiler without stdint.h, i.e. as int or long and if it would matter.
They are not compatible types, which you can see with a a simple example:
int* iptr;
long* lptr = iptr; // compiler error here
So it mostly matters when dealing with pointers to these types. Similarly, there is the "strict aliasing rule" which makes this code undefined behavior:
int i;
long* lptr = (long*)&i;
*lptr = ...; // undefined behavior
Some another subtle issue is implicit promotion. In case you have some_int + some_long then the resulting type of that expression is long. Or in case either parameter is unsigned, unsigned long. This is because of integer promotion through the usual arithmetic conversions, see Implicit type promotion rules.
Shouldn't matter most of the time, but code such as this will fail: _Generic(some_int + some_long, int: stuff() ) since there is no long clause in the expression.
Generally, when assigning values between types, there shouldn't be any problems. In case of uint32_t, it doesn't matter which type it corresponds to, because you should treat uint32_t as a separate type anyway. I'd pick long for compatibility with small microcontrollers, where typedef unsigned int uint32_t; will break. (And obviously, typedef signed long int32_t; for the signed equivalent.)
The types long and int have different ranks. The rank of the type long is higher than the rank of the type int. So in a binary expression where there are used an object of the type long and an object of the type int the last is always converted to the type long.
Compare the following code snippets.
int x = 0;
unsigned int y = 0;
the type of the expression x + y is unsigned int.
long x = 0;
unsigned int y = 0;
the type of the expression x + y is unsigned long (due to the usual arithmetic conversions) provided that sizeof( int ) is equal to sizeof( long).
This is very important in C++ than in C where function overloading are allowed.
In C you have to take this into account for example when you are using i/o functions as for example printf to specify a correct conversion specifier.
Even on platforms where long and int have the same representation, the Standard would allow compilers to be willfully blind to the possibility that the act of storing a value to a long* might affect the value of an int* or vice versa. Given something like:
#include <stdint.h>
void store_to_int32(void *p, int index)
{
((int32_t*)p)[index] = 2;
}
int array1[10];
int test1(int index)
{
array1[0] = 1;
store_to_int32(array1, index);
return array1[0];
}
long array2[10];
long test2(int index)
{
array2[0] = 1;
store_to_int32(array2, index);
return array2[0];
}
The 32-bit ARM version of gcc will treat int32_t as synonymous with long and ignore the possibility that passing the address of to array1 to store_to_int32 might cause the first element of that array to be written, and the 32-bit version of clang will treat int32_t as synonymous with int and ignore the possibility that passing the address of array2 to store_to_int32 might cause that array's first element to be written.
To be sure, nothing in the Standard would prohibit compilers from behaving in that fashion, but I think the Standard's failure to prohibit such blindness stems from the principle "the dumber something would be, the less need there should be to prohibit it".

Make 7 year ago prints compatible with now

I'm reading the book The Art Of Exploitation and there is a program called memory_segments.c which just demostrante to us how the memory segments works, the heap, the stack and etc. But when I try to compile the program it seems like that the printing isn't more compatible with now. I use gcc 10.2.0 to compile my C code.
#include <stdio.h>
#include <stdlib.h>
int global_initialized_var = 5;
int main()
{
printf("global_initialized_var is at address 0x%08x\n", &global_initialized_var);
// ... more prints, removed just to make code shorter
return 0;
}
// warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘int *’
The author of the book's output:
...
global_initialized_var is at address 0x080497ec
...
What is the alternative for 0x%08x? And why does 0x%08x no longer work?
%x prints integer values not pointers. It is Undefined Behaviour. To print pointers use printf("%p", (void *)pointer);
The integer types which should be used when converting pointers are uintptr_t or intptr_t. For differences between pointers ptrdiff_t
To print uintptr_t or intptr_t you need to use PRIdPTR PRIiPTR PRIoPTR PRIuPTR PRIxPTR PRIXPTR printf formats. Example:
uintptr_t p = SOME_VALUE;
printf("UINTPTR_T printed as hex: %" PRIxPTR "\n", p);
What is the alternative for 0x%08x?
To properly print a pointer value, use %p with void* pointer:
printf("%p\n", (void*)&global_initialized_var);
It will use some implementation-specific form of printing it, most commonly it's the same as %#x format. If you want to get a specific numeric representation of the pointer, the best standard way would be to use the biggest possible integer type:
printf("%#jx\n", (uintmax_t)(uintptr_t)&global_initialized_var);
However, usually you can/want to just cast the pointer value to an integer and print it, as usually it's only for temporary debugging purposes, just like:
// Could truncate pointer value!
printf("%#llx\n", (unsigned long long)&global_initialized_var);
printf("%#lx\n", (unsigned long)&global_initialized_var);
printf("%#x\n", (unsigned)&global_initialized_var);
Note that unsigned has at least 16 bits, long has at least 32-bits and long long has at least 64-bits. Platforms are nowadays 64-bit, pointers are 64-bit - anyway, prefer the widest available type when printing pointer value, to be portable - thus the use uintmax_t above. It was common on unix platforms to use long to cast to/from pointers - on 64-bit unix long has 64-bits, while on windows it has 32-bits.
why does 0x%08x no longer work?
It is (and was) UB due to mis-matched types.
%x expects an unsigned. &global_initialized_var is a pointer.

Why type casting not required?

Below are following code written in c using CodeBlocks:
#include <stdio.h>
void main() {
char c;
int i = 65;
c = i;
printf("%d\n", sizeof(c));
printf("%d\n", sizeof(i));
printf("%c", c);
}
Why when printing variable c after it was assigned with int value (c = i), there no need for casting to be made?
A cast is a way to explicitly force a conversion. You only need casts when no implicit conversions take place, or when you wish the result to have another type than what implicit conversion would yield.
In this case, the C standard requires an implicit conversion through the rule for the assignment operator (C11 6.5.16.1/2):
In simple assignment (=), the value of the right operand is converted to the type of the
assignment expression and replaces the value stored in the object designated by the left
operand.
char and int are both integer types. Which in turn means that in this case, the rules for converting integers are implicitly invoked:
6.3.1.3 Signed and unsigned integers
When a value with integer type is converted to another integer type other than _Bool, if
the value can be represented by the new type, it is unchanged.
Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or
subtracting one more than the maximum value that can be represented in the new type
until the value is in the range of the new type.
Otherwise, the new type is signed and the value cannot be represented in it; either the
result is implementation-defined or an implementation-defined signal is raised.
In your case, the new type char can be either signed or unsigned depending on compiler. The value 65 can be represented by char regardless of signedness, so the first paragraph above applies and the value remains unchanged. For other larger values, you might have ended up with the "value cannot be represented" case instead.
This is a valid conversion between integer types, so no cast is necessary.
Please note that strictly speaking, the result of sizeof(c) etc is type size_t and to print that one correctly with printf, you must use the %zu specifier.
This assignment is performed on compatible types, because char is not much more than a single byte integer, whereas int is usually 4bytes integer type (machine dependant). Still - this (implicit) conversion does not require casting, but You may loose some information in it (higher bytes would get truncated).
Let's examine your program:
char c; int i = 65; c = i; There is no need for a cast in this assignment because the integer type int of variable i is implicitly converted to the integer type of the destination. The value 65 can be represented by type char, so this assignment is fully defined.
printf("%d\n", sizeof(c)); the conversion specifier %d expects an int value. sizeof(c) has value 1 by definition, but with type size_t, which may be larger than int. You must use a cast here: printf("%d\n", (int)sizeof(c)); or possibly use a C99 specific conversion specifier: printf("%zu\n", sizeof(c));
printf("%d\n", sizeof(i)); Same remark as above. The output will be implementation defined but most current systems have 32-bit int and 8-bit bytes, so a size of 4.
printf("%c", c); Passing a char as a variable argument to printf first causes the char value to be promoted to int (or unsigned int) and passed as such. The promotion to unsigned int happens on extremely rare platforms where char is unsigned and has the same size as int. On other platforms, the cast is not needed as %c expects an int argument.
Note also that main must be defined with a return type int.
Here is a modified version:
#include <stdio.h>
#include <limits.h>
int main() {
char c;
int i = 65;
c = i;
printf("%d\n", (int)sizeof(c));
printf("%d\n", (int)sizeof(i));
#if CHAR_MAX == UINT_MAX
/* cast only needed on pathological platforms */
printf("%c\n", (int)c);
#else
printf("%c\n", c);
#endif
return 0;
}

what happens if buffer's data type mismatches (int * versus unsigned int *) when calling a function?

So suppose one has function:
int somefunc(int* buf)
but we call somefunc by:
somefunc(buf2)
where buf2 is heap buffer, allocated by alloca, that has data of type unsigned int (thus pointer buf2 is of type unsigned int*).
What is the danger of doing this? What would be the problem here? I do think this might have to do with heap(buffer) overflow, but I am not sure what that would be. There may be some other problems with the code, and if so, I do want to know about these problems too.
Example code:
void typecastfunc(FILE *p, int *buf, unsigned int index) {
unsigned int a;
for (a = 0; a < index; a++) {
if (fread(&buf[a], sizeof(unsigned int), 1, p) < 1) {
break;
}
}
}
void caller(char *filen) {
FILE *p = fopen(filen, "rb");
if (!p) {
return;
}
unsigned int index;
fread(&index, sizeof(unsigned int), 1, p);
unsigned int *buf = alloca(index * sizeof(unsigned int));
if (!buf) {
return;
}
typecastfunc(p, buf, index);
}
int main(int argc, char *argv[]) {
caller(argv[1]);
}
The question is being asked, because by randomly fuzzing, I was able to get the signs of overflow, but I do not know how overflow can occur by type mismatch. Other codes seem harmless, so I assume it must be because of type mismatch.
You are reading data from a binary stream into an array of int by chunks of size sizeof(unsigned int).
While it would be better to use the proper type, there is no bad consequences to expect from this particular mistake as the size of int is guaranteed y the standard to be identical to the size of unsigned int.
The representation of unsigned int might produce unexpected behavior when the values are used as int and exceed the range of this type, but for most current architectures it does not pose a real problem.
There is always a danger when mixing signed and unsigned versions of the same integer type. Implicit conversions can cause unintentional change of signedness. Calculations can turn up unsigned when you wanted them signed or vice versa. A value which is representable by the unsigned type might not be representable by the signed type. And so on.
As for the specific case of pointers to signed/unsigned versions of the same type (int in this case), there are no additional dangers apart from what is mentioned above. These pointer conversions are considered to be safe, as a special exception in the C standard (1).
So converting between int* to/from unsigned int* is safe in itself. Please note that this is not safe if using two different types though! A conversion from int* to short* for example, can cause all manner of subtle bugs.
(1) In C standard gibberish, C11 6.5/7 ("the strict aliasing rule"):
"An object shall have its stored value accessed only by an lvalue
expression that has one of the following types:"
...
- "a type that is the signed or unsigned type corresponding to the effective type of the object"

What are the automatic type promotions of variadic function arguments?

Consider the following code snippet:
#include <stdio.h>
#include <stdarg.h>
void display(int num, ...) {
char c;
int j;
va_list ptr;
va_start(ptr,num);
for (j= 1; j <= num; j++){
c = va_arg(ptr, char);
printf("%c", c);
}
va_end(ptr);
}
int main() {
display(4, 'A', 'a', 'b', 'c');
return 0;
}
The program gives runtime error because vararg automatically promotes char to int, and i should have used int in this case.
What are all types are permitted when I use vararg, how to know which type to use and avoid such runtime errors.
another case that the others forgot to mention are pointer types, critical is NULL in particular. Since this could expand to 0 or (void*)0 (or some other weird things) you will not know if the compiler puts an int or a void* in the list. Since these can have different width, this can lead to annoying bugs.
You can use any standard type with va_arg except char, signed char, unsigned char, short, unsigned short, _Bool, and float. It's possible that an implementation defines additional nonstandard types that also have integer conversion rank lower than int, or likewise nonstandard small floating-point types, but you would not need to be aware of these unless you intend to use them, so for practical purposes the list I gave is complete.
While using va_arg the char is promoted to int. There are other types(the list given by #R..) which are promoted.
so in order to read it as a char you have to do typecast.
like:
c = (char) va_arg(ap, int);
for the complete example please see:
http://en.cppreference.com/w/cpp/language/variadic_arguments

Resources