So you think you know C: the Ksplice Pointer Challenge
blogs.oracle.com
blogs.oracle.com
I could have used the hint about what the output of %p looks like (I missed the leading 0x). Of course nobody's keeping score, but that doesn't seem essential to the question.
I thought about explicitly stating "sizeof(int) is 4 on this system" in the intro blurb, but that primes you a bit more than I'd like for the answer to #2, so I thought it was a little cleaner not to.
But I'm more annoyed that I failed the last two by not understanding C than that I failed the second by guessing sizeof(int) incorrectly.
Either way, after I realised that sizeof(int) == 4 the test was surprisingly simple. Are pointers really that hard to grasp?
Your point (that int isn't a qword on all 64bit architectures) still stands. But your statement is potentially incorrect.
sizeof(char) == 1
sizeof(short) == 2
sizeof(int) == 4
sizeof(long) == 4
sizeof(long long) == 8
sizeof(void* ) == 4
Typically on 64-bit: sizeof(char) == 1
sizeof(short) == 2
sizeof(int) == 4
sizeof(long) == 8
sizeof(long long) == 8
sizeof(void* ) == 8It should also be noted that this has nothing to do with the underlying hardware. Instead it's a decision made by the OS ABI. There's nothing that says you can't have sizeof(int) == 8 on a 32-bit system.
Edit: also, pedantry: the assignment of types to sizes is part of the ABI, not the architecture.
Short answer: if sizeof(int)=8, then you lose either the 2 byte integer, or the 4 byte integer. This would make it harder to minimize memory footprint in programs which work with large amounts of data.
The reason for this is that char, short, int, long, and long long are the only names you have for various integer sizes. Since sizeof(char) is fixed at 1 and sizeof(char) <= sizeof(short) <= sizeof(int) if you make int eight bytes, you must either drop the two-byte or four-byte integer since you only have one name left for it. Dropping one of those types will make programmers unhappy since they packing structs careful is a valid thing to do for memory minimization in large-memory programs.
Note that int32_t and friends are not valid names since they MUST be a typedef for one of char, short, int, long or long long.
Disclaimer: if C11 has fixed this, I'm ignorant of it.
That said, it wouldn't make it any less insane; so no one does this and in fact int is 32 bits everywhere except microcontrollers (and 8086, for the tiny handful of people writing BIOS or bootloader code).
That's not really the issue though. My point was that the choice of ILP64 vs. LP64 on a single architecture could not cause you to "lose" a 16 bit quantity. It can't, because those machine instructions obviously don't go away when you change your compiler's calling conventions. So a C99-compliant compiler would still be required to provide int16_t.
Which is... maybe too much minutiae even for a C minutiae thread. But it was my point, anyway.
This seems to be common state of things on almost anything, that is designed to be fast first and "C-compatible" second.
Of course, that's the kind of logic that got us into this situation today. Fortunately we should to be able to live with CHAR_BIT == 8 for the forseeable future.
Also, the C standard "Sizes of Integer Types" section sets out minimum ranges each type must allow, and I think int has always been +/-32767. ie see the section at http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
What commonly used platforms have a 64-bit ints? The only one I vaguely recall are really, really old versions of Solaris. After a while IIRC they decided that ILP64 was too much of a PITA and went with LP64.
Apparently some DSPs cannot address an 8-bit quantity -- they're not easy to find but I've found a comp.lang.c posting where Jack Klein mentions a 32-bit Sharp DSP with CHAR_BIT = 32 (so sizeof(char) == sizeof(int) == 1 !) and a Texas Instruments TMS32F28xx DSP with a 16 bit CHAR_BIT.
On such a system, C99 might not define int8_t but you could use int8_least8_t.
My guess is that they are more common than 64-bit ints but they might be supported by their own weird C Compiler/toolchain.
int c;
while ((c = getchar()) != EOF) {
does not work properly on them.Only values of type void* are valid arguments in case of the %p conversion specifier as there need not be a uniform pointer representation.
pointer-test.c:7:3: warning: format ‘%p’ expects type ‘void * ’, but argument 2 has type ‘int * ’
Same goes for using -std=c99 instead of -ansi, although casting to (void *) does get rid of the warning.
So in the spirit of performing that experiment and exploring this interview anti-pattern, here's my counter-challenge, which I argue is intellectually equivalent:
#include <stdio.h>
void
foo()
{
printf("in foo\n");
}
void
main()
{
void (*func)(void) = foo;
(************************************func)();
}
What does that program do? Yeah, exactly: you just ran it. And you're surprised, aren't you? And most importantly: who cares? Certainly not I when I'm interviewing you -- where you can trust I will ask you deeper questions than language arcana...Keep in mind that this is Ksplice; for the work they do, this might not be meaningless arcana.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
int *x = malloc(sizeof(int) * 5);
memset(x, 0, sizeof(int) * 5);
printf("%d\n", *x);
printf("%d\n", *(x+1));
printf("%p\n", x);
printf("%p\n", x+1);
printf("%p\n", &x);
printf("%p\n", &x+1);
return 0;
} #include <stdio.h>
#include <stdlib.h>
#include <string.h>
void foo(int x[5]) {
printf("%p\n", x);
printf("%p\n", x+1);
printf("%p\n", &x);
printf("%p\n", &x+1);
}
int main() {
int x[5];
foo(x);
return 0;
} #include <stdio.h>
void foo(int (*x)[5]) {
printf("%p\n", *x);
printf("%p\n", *x+1);
printf("%p\n", x);
printf("%p\n", x+1);
}
int main(void) {
int x[5];
foo(&x);
return 0;
}Though I used to program a lot of C, I'm not a C wizard by a longshot, so I might be wrong. Are there cases where using expressions based on &x, where x is an array name, is idiomatic C?
edit: Stupid mistake, see cygx's reply (sizeof(x) gives the size of the array x in bytes, not in elements).
x + sizeof x / sizeof *x
which is equivalent to (foo *)((char *)x + sizeof x)
whereas x + sizeof x
is equivalent to (foo *)((char *)x + sizeof (foo) * sizeof x))
The expression &x + 1
is not idiomatic as it has the type pointer-to-array-of-foo instead of pointer-to-foo.As to your final question: Parameter declarations discard the size of array types - they are actually pointer-declarations in disguise.
To enforce a fixed array size, you need to declare a parameter of type pointer-to-array, eg
void bar(int (*arg)[42]);
which you'd have to call like this: int x[42] = { 0 };
bar(&x); *(&x + 1)
I would be hard-pressed to call that idiomatic, however.Multidimensional arrays are arrays of arrays, not arrays of pointers to arrays (as in Java). Therefore when manipulating, say, rows of a 2-dimensional array you are dealing with pointers to arrays like &x in this example.
It's not an every-day thing but it does come up, and in some specialised fields no doubt it is very common.
Have to be careful there. Going more than one after the end of the array is undefined (see 6.17):
http://c-faq.com/~scs/cgi-bin/faqcat.cgi?sec=aryptr
Actually, that whole FAQ should be of interest to anyone who liked this article. No matter how many times I read through it, I seem to find something new.
In graphics programming, or more specifically, file format programming, sometimes I have to parse through, or generate a colortable and then go through a bunch of serialized data and transform that into a buffer where I have something like coordinate[x][y]{[z]}.{rgb/cmyk/yuv/hsv/rgba/bgr...} and I have a bunch of unions and structs; having char * data[3] as my payload and then being able to offset around into it is fantastically convenient, especially when trying to apply kernels or do transformations over the entire space.
The code is far more readable at the lower level manipulation with this kind of stuff.
And when you say 'Well I use OTS solutions for graphics' and I say "I do too, when I can. But when I can't, it's nice to be able to express what I need to do effeciently"
In question 2 I got tripped up because I assumed sizeof(int) == 8 on a 64 bit system. I also got question 4 wrong because I didn't know that &x gives a pointer to an array of size 5.
Also confusing was the first time I saw this wonderful construct:
void f(int x){
char buf[x + 1];
printf("%zd\n", sizeof(buf))
}
Not only does it work, it's also correct C99!Writing C involves more care than usual because there are so many weird things that are easy to not understand, and not understanding those will cause subtle undefined behavior that allows people controlling the input data to 0wn your computer. Frightening.
Just to make it confusing though, you can't pass an array by value to a function. It gets automatically converted to a pointer. Arrays are unique in the C language in this sense, and it contributes to the illusion that arrays and pointers are the same thing. (Try passing an array by reference, though, and all becomes clear.)
And in this age, with technology so advanced, and computing resources so inexpensive ... why? Because it's the geek's athletic challenge? Whomever's brain holds the most memorized facts is the smartest brain in the world?
I'd guess someone would answer "because you need to know this stuff to be good at your programming job!" To which I reply: if you ever make the assumption that you know how some bit of code will work in a system, you're well on your way to becoming the infallible coder that no one likes to work with. This is why we have testing methodologies.
Yes, you need to be aware of this particular nuance of C. My answers were more high-level ("the address of the beginning of the array", "the next int in memory, not the next byte", etc) and I have no need to know the precise memory locations when I can ask the computer to tell me.
Can I get a job with the Ksplice team? :)
int aiFoo[ 5 ] = { 1 }; printf( "%d", aiFoo[ 1 ] ); static int ls_aiFoo[ 5 ]; printf( "%d", ls_aiFoo[ 1 ] );
or how about int i = 5; int aiFoo[ i ]; which is valid C99 but not C89?
At any rate - much more important than learning the minutia of C is learning to get things done. If you are passionate about writing some OS you don't need to be taught - you would already know or be learning. It costs nothing but time...
Question 2: I didn't think that x+1 would be interpreted as a pointer for some reason, so I guessed 0x7fffdfbf7f01. Wrong.
Question 4: I incorrectly thought that what I remembered about pointer arithmetic would apply here. 1 * sizeof(int) = 0x04, so I guessed 0x7fffdfbf7f04. Wrong.
I don't work in C professionally, but I'd like to not forget things. My error in question 2 shows forgetfulness, and my error in question 4 is from not ever completely mastering every nook & cranny in C.
How did the rest of HN do?
Your pointer arithmetic was correct. What you missed was that &x is a pointer to an array of five ints (so, sizeof(int[5])), not a single int.
I on the other hand didn't realize sizeof(int) is not necessarily 8 on 64-bit machines. You learn something new every day...
int (*y)[5];
y = &x;
printf("%p, %p\n", y, y+1);" That is, whenever an array appears in an expression, the compiler implicitly generates a pointer to the array's first element, just as if the programmer had written &a[0] "
http://c-faq.com/aryptr/aryptrequiv.html
Also remember that much fun could be had by studying the pre-processor up close .. another source of mind-bending fun.
The takeaway summed the point up nicely although everyone's arguing over sizeof(int). Sure, there are cases when sizeof(int) is not 4 but that's not the point of the exercise. If you solved believing it was 8, you'd still be correct in my opinion.
I'm reminded of so many great articles with comments like "it's you're and not your"
Array vs. pointer is one of the more advanced interview questions I like to use to probe C knowledge. I don't actually expect any correct answers, just the knowledge that array != pointer.
Learned something, so thanks for the link.
Maybe time to bust out my K&R again.
*(x+1)
that is equivalent to x[1]? Here, in principle, you do x+1 before applying *.EDIT
HN formatting magic.
#include <stdio.h>
int main()
{
int x[5] = {1,2,3,4,5};
printf("%d, %d\n", x[1],1[x]);
return 0;
}"Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined."
In short, the moment you write a variable of array type and it's not one of those very limited cases, it immediately becomes a pointer to the first element. x+1 is completely valid.