I don't follow you here. To me, saying something like "StringsByID = malloc(NumStrings * sizeof(void *));" is going to be the same no matter what system you're running.
Well for starters, if you start writing things to files and you want the files to be binary compatible across platforms, forget doing something like
fwrite((void *)arrayOfInts, sizeof int , numElementsInArray, file);
fwrite((void *)&mystruct, sizeof(mystruct), 1, file);
Problems? Well let's see:
1) the bytes that make up an int are stored differently on different platforms. Intel (x86) use little-endian, meaning least significant byte first. Other CPUs such as PowerPC and Motorola 68xxx (for Macs) use big-endian, meaning most significant byte first. Reading and writing the bytes out directly like the code above means files created by your program on one platform will not read with the correct values on another.
2) struct is another example. Here the problem is that the C language leaves the alignment of members within a struct up to the compiler. Meaning the members in a struct don't have to come in consecutive bytes; there could be unused bytes between members to optimize perf based on the CPU. In other words, given a struct
struct a
{
char a;
int b;
}
Even sticking to one particular CPU where int is 4 bytes, sizeof(struct a) can still differ depending on compiler and compiler settings. For example, it's likely that the compiler will try to align the int to 4-byte boundary by adding 3 unused bytes between char a and int b, making the struct 8 bytes.
3) speaking of ints, guess what the language says about sizeof(int)? Absolutely nothing. You are saved mainly by the fact that most people currently has 32-bit CPUs, where sizeof(int) is likely 4 bytes.
Another area where C is notorious unspecific relates to order of operations. Again this is not even a cross-platform issue; it can vary between compilers. For a sampling, consider the following examples from the
C FAQ:
3.1 Why doesn't this code:
a = i++;
work?
3.2 Under my compiler, the code
int i = 7;
printf("%d\n", i++ * i++);
prints 49. Regardless of the order of evaluation, shouldn't it print 56?
3.3 I've experimented with the code
int i = 3;
i = i++;
on several compilers. Some gave i the value 3, and some gave 4. Which compiler is correct?
3.7 Why did
printf("%d %d", f1(), f2());
call f2 first? I thought the comma operator guaranteed left-to-right evaluation.
The danger here is that if you are unaware of these sorts of things and don't take care to avoid these sort of unspecified expression evaluation order in your code, you can end up writing something that works fine on one compiler (say, the one you are using to compile for x86), but inexplicably works differently on another compiler (say, the one you use for compiling for Mac/PowerPC).
Finally, many popular compilers (like Visual C++ from Microsoft) often add a lot of extra stuff on top of the standard, such as compiler-specific preprocessor directives, extra library functions outside of the standard, etc. It is possible for someone to learn C using a specific compiler and unaware that some of the things they are using are compiler-specific or runtime-library specific, and then end up with source code that fail to compile on another compiler, or worse, compile but doesn't work properly during runtime. This is especially easy to do with runtime library functions, since it's easy for someone to look up one in their compiler's help documentation and use it, without paying attention to whether that library function is actually in the standard and therefore supported by all standard-compliant compilers.
============
In summary, it is possible to write portable code in C, but it is not automatic from merely learning to program in C; you need to be conscientious of these "gotchas" hidden in the language that can work against you in code portability.