The assignment b = a+6
cannot work, as b is not a pointer, it is the address of an array, so you cannot assign to it. When you declared it as char *b;
then the assignment works, as you can assign to a pointer.
Your function call will work because you are passing a pointer (to the 6th element of array a), and your function foo() takes a char* as its argument.
Consider what the assembly language will look like: your variable a will become some memory (let's say beginning at 0x1000) holding the following:
0x1000 'H'
0x1001 'e'
0x1002 'l'
...
0x1009 'l'
0x100a 'd'
0x100b '\0'
Your variable b will be a further 15 bytes of memory, beginning at 0x100c.
But a and b are not pointers, they are simply the values 0x1000 and 0x100c. b = a+6
is the equivalent of saying 0x100c = 0x1000 + 6
which is clearly nonsense.
When you declare b as char *b;
you end up with some number of bytes (4 or 8 most likely--32 or 64 bits) which will hold the address of a character. So while b will still be at 0x100c, it now holds an address of memory, so when you write b = a+6
you will be writing the number 0x1006 into the memory at 0x100c.
Understanding the difference between pointers and arrays is one of the most confusing parts of C, but if you think about what is happening at the machine level it's pretty easy to keep things straight.