[Solved] Passing string to a function when unsigned int expected in C


The whole point of a type in a language like C is that it describes some well-defined, useful set of values.

A value of type unsigned int can hold any integer in a range defined by your compiler and processor. This is typically a 32-bit integer, meaning that an unsigned int can hold any integer from 0 to 4294967295. But an unsigned int can not hold the value 5000000000 (it’s too big), or the value 123.456 (it’s not an integer) or the value “hello, world” (strings aren’t integers).

A value of type char * can hold a pointer to a character anywhere in the usable address space on your computer. So it can hold a pointer to a single character, or it can hold a pointer to a null-terminated array of characters like “hello, world”, or it can hold a NULL pointer. But it is not intended to hold an integer, or a floating-point value.

Sometimes, under constrained or unusual circumstances, programmers try to bend the rules, by wedging a value of one type into a variable of a different type. Sometimes you can make this work, sometimes you can’t. It’s almost always a significantly bad idea. Even if it can be made to work, it’s often the case that it works properly on one machine, but not others.

Let’s look more carefully at what you’re doing. (I’m filling in a few details you left out.)

void expects_unsigned_int(unsigned int);

Here we tell the compiler that there’s going to be a function named expects_unsigned_int that accepts one argument of type unsigned int and returns nothing.

#include <stdio.h>

int main()
{
    expects_unsigned_int("some text");
}

Here we call that function, passing an argument of type char *. We’re in trouble already, of course. You can’t wedge a char * into an unsigned int sized slot. A proper compiler will give you a serious warning, if not an outright error, here. Mine says

warning: passing argument 1 of ‘expects_unsigned_int’ makes integer from pointer without a cast
expected ‘unsigned int’ but argument is of type ‘char *’

These warnings make sense, and are consistent with my explanations so far of what we should and shouldn’t do with types.

As you may know, a pointer is “just” an address, and on most machines an address is “just” a bit pattern of some size, so you can convince yourself that it ought to be possible to jam a pointer into an integer. The key question, which we’ll return to in a minute, is whether type unsigned int is literally big enough to hold all possible values of type char *.

void expects_unsigned_int(unsigned int val) {

Here we begin defining the details of function expects_unsigned_int. Again we say that it accepts one argument of type unsigned int and returns nothing. That’s consistent with the earlier prototype declaration. All right so far.

unsigned int* string = 0;

Here we declare a pointer of type unsigned int * and initialize it to the null pointer. We don’t really need this intermediate pointer, and in this case it doesn’t matter whether we initialize it, since we’re about to overwrite it.

string = (unsigned int*) val;

Here’s where the trouble begins. We have an unsigned int value, and we attempt to convert it into a pointer. Again, this might seem reasonable, since pointers are “just” addresses and addresses are “just” bit patterns.

The other thing we have is an explicit cast. In this case, surprisingly, the cast is not really “doing” the conversion from unsigned int to unsigned int *. If we wrote the assignment without the cast, like this:

string = val;

the compiler would see an unsigned int value on the right-hand side, and a pointer of type unsigned int * on the left-hand side, and it would attempt to perform the same conversion implicitly. But since it’s a dangerous and potentially meaningless conversion, the compiler would warn about it. Mine says

warning: assignment makes pointer from integer without a cast

But when you write an explicit cast, for most compilers what this means is, “trust me, I know what I’m doing, do this conversion and keep your doubts to yourself, I don’t want to hear any of your warnings.”

Finally,

printf("%s", (char*)string);

Here we do two things. First we explicitly convert the unsigned int * pointer into a char * pointer. That’s also a questionable conversion, but of a much lesser concern. On the vast majority of computers today, all pointers (no matter what they point to) have the same size and representation, so a conversion like this is most unlikely to cause any problems.

And then the second thing we do is, finally, try to print the char * pointer using printf and %s. As you’ve discovered, it doesn’t always work. It doesn’t work for me on my computer, either.

There are computers where it would work, so the answer to your question “Is it possible to do it?” is “Yes, maybe, but.”

Why didn’t it work for you? I can’t be sure, but it’s probably for the same reason it didn’t work for me. On my machine, pointers are 64 bits, but regular ints (including `unsigned int) are 32 bits. So when we called

expects_unsigned_int("some text");

and attempted to wedge a pointer into an int-sized slot, we scraped off 32 of its 64 bits. That’s an information-losing transformation, so it’s very likely to be an unrecoverable error.

Let’s print some additional information, so we can confirm that this is what’s going on. I encourage you to make these modifications to your program on your computer, so you can see what results you get.

Let’s rewrite main like this:

int main()
{
    char *string = "some text";
    printf("string = %p = %s\n", string, string);
    printf("int: %d, pointer: %d\n", (int)sizeof(unsigned int), (int)sizeof(string));
    expects_unsigned_int(string);
}

We’re using the printf format %p to print the pointer. This will show us a representation of the bit pattern that makes up the pointer value (however big it is), typically in hexadecimal. We’re also using sizeof() to tell us how big ints and pointers are on the machine we’re using.

Let’s rewrite expects_unsigned_int like this:

void expects_unsigned_int(unsigned int val) {
    char *string = val;
    printf("val = %x\n", val);
    printf("string = %p\n", string);
    printf("string = %s\n", string);
}

Here we’re printing both the value of val as it comes in, and the pointer we recover from it (again, using %p). Also, I’m making string of type char *, since there was no point in having it unsigned int *.

When I run the modified program, here’s what I get:

string = 0x101295f20 = some text
int: 4, pointer: 8
val = 1295f20
string = 0x1295f20
Segmentation fault: 11

Immediately we see several things:

  • Pointers are bigger than ints on this machine (as I was saying earlier). There’s no way we’re going to be able to stuff a pointer into an int without potentially losing data.
  • We’re indeed scraping off some of the bits of the sting pointer. It starts out being 101295f20 and ends up as 1295f20.
  • The program doesn’t work. It crashes with a segmentation violation, likely because the mangled pointer value 0x1295f20 points outside its address space.

So how do we fix this? The best way would be to not try to pass a pointer value through a slot that’s designed to hold integers.

Or, if we really wanted to, if we were bound and determined to convert pointers to integers and back again, we could try using a bigger integer, such as an unsigned long int. (And if that wasn’t big enough, we could also try unsigned long long int.)

I rewrote main like this:

void expects_unsigned_long_int(unsigned long int val);

int main()
{
    char *string = "some text";
    printf("string = %p = %s\n", string, string);
        printf("int: %d, pointer: %d\n", (int)sizeof(unsigned long int), (int)sizeof(string));
    expects_unsigned_long_int(string);
}

And then expects_unsigned_long_int looks like this:

void expects_unsigned_long_int(unsigned long int val) {
    char *string = val;
    printf("val = %x\n", val);
    printf("string = %p\n", string);
    printf("string = %s\n", string);
}

I still get warnings when I compile it, but now when I run it it prints

string = 0x10a09df20 = some text
int: 8, pointer: 8
val = a09df20
string = 0x10a09df20
string = some text

So it looks like type unsigned long int is big enough (for now), and no bits get scraped off, and the original pointer value is successfully recovered inside expects_unsigned_long_int, and the string prints correctly.

But, in closing, please, figure out a better way of doing this!

1

solved Passing string to a function when unsigned int expected in C