| March 10, 2009 9:00 PM PDT | |
Avoid limitations due to the requirement in ANSI C that parameters be passed as either intor doubleif functions are not protoyped. Passing parameters as either int or double might be incorrect for your actual code logic.
The following sample code does not contain explicit prototyping:
//modulel.c
extern int intitialize_buffer();
void main(void)
{
char*pd1=malloc(1000);
char*p2=malloc(2000);
int size=p2-p1;
initialize_buffer(size)
}
//module2.c
char * global_buffer;
int initialize_buffer( SIZE_T size)
}
global_buffer=(char*) malloc(size);
if (global_buffer==NULL) return(-1);
return(0);
}
Explicitly prototype all functions; this guideline applies to all programming platforms. Explicit prototyping also provides these benefits:
- It ensures that the caller and the callee have matching argument types, because the compiler will evaluate the types and generate warnings for mismatched types.
- It protects against parameter misuse that can lead to parameter corruption.
- The compiler can more efficiently pass parameters to called functions.
Note that you should explicitly prototype a function in a header file to save time.
The following sample code is a revised version of the code given in the "Challenge" section that illustrates prototyping:
//modulel.c
extern int intitialize_buffer(SIZE_T);
void main(void)
{
char*pd1=malloc(1000);
char*p2=malloc(2000);
SIZE_T size=p2-p1;
initialize_buffer(size)
}
//module2.c
char * global_buffer;
int initialize_buffer( SIZE_T size)
}
global_buffer=(char*) malloc(size);
if (global_buffer==NULL) return(-1);
return(0);
}
At line 2, the prototype explicitly specifies the kind of parameters to be received by the function. In the code sample given in the "Challenge" section, the size defaults to int, but in the code for 64-bit Intel architecture, it is a scalable type, SIZE_T, which ensures that the code can be easily compiled for either 32-bit or 64- bit architectures.
At line 8, size is specified as type int in the code without explicit prototyping. The int declaration limits the difference between two pointers to no greater than 232. This limit becomes a problem when the code is compiled for the 64-bit Intel® architecture, because values greater than the limit will be truncated when passed to size. In the revised code, a scalable type, SIZE_T, is used to ensure that the size of the difference between the pointers adapts to the architecture. When compiled for the 64-bit Intel architecture, the difference between pointers can be up to 264, ensuring that the upper 32 bits won't be lost.
At line 13, the callee expects a SIZE_T parameter, which is a 64-bit value when compiled for Windows 2000 (64-bits)*. Since the int variable in the code sample given in the "Challenge" section supplies a value only for the low order 32 bits; the upper 32 bits contain unknown information. In the revised code, the callee expects and receives a 64-bit value. When compiled for the 32-bit Intel architecture, SIZE_T will ensure that the callee receives a properly sized value.
Considerations for explicit prototyping in floating-point functions are addressed in a separate item, Explicit Prototyping for Floating-Point Functions.
Preparing Code for the IA-64 Architecture (Code Clean)
For more complete information about compiler optimizations, see our Optimization Notice.
Comments (6) 
| July 1, 2009 7:55 PM PDT
gangti |
sorry, original text have some errors, read the following please... file.c ================================================== #undef NULL #define NULL 0 void main() { func(NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); } void func(char *arg1, char *arg2, char *arg3, char *arg4, char *arg5, char *arg6, char *arg7, char *arg8, char *arg9) { // } ================================================== When I port above code from x86 to x64/ia64, I found: A. In windows x64 platform, when compiled with cl (vs2005's x64 cross compiler)the first four param will be transferred to func with universal registers, using mov(same as movzx) assembly instructions, so the upper 32 bits will be zeroed. So in the above code, arg1~arg4 will be transferred correctly, although arg5~arg9 may have problem. But in linux x64 platform, when compiled with cc(gcc's component), I found the first sixth parm will be transferred with universal registers. So in the above code, arg1~arg6 will be transferred correctly, although arg7~arg9 may have problem. B. In windows ia64 platform, when compiled with cl (vs2005's ia64 cross compiler)the first eight param will be transferred to func with universal registers, using mov(upper bits will be signed extended)assembly instruction. In this case, that is 0 extended for the upper bits. The other params will be put in the stack memory, but they will be first put to registers, and then stack memory. So in the above code, all params will be transferred correctly. So the questions is, whether I am right or not? May I rely on the knowledge of intermediate assembly code, and just modify the code as follows for windows x64?????? ================================================== #undef NULL #define NULL 0 void main() { func(NULL,NULL,NULL,NULL,(char*)NULL, (char*)NULL,(char*)NULL,(char*)NULL,(char*)NULL); } void func(char *arg1, char *arg2, char *arg3, char *arg4, char *arg5, char *arg6, char *arg7, char *arg8, char *arg9) { // } ================================================== Another questions is that, why linux x64 put the first six params to registers while windows x64 put the first four params to registers???? The hardware(cpu especially) is the same, so why shouldn't the calling conventions is the same???? --the end-- |
| July 2, 2009 11:12 AM PDT
srimks
|
The calling convention for 64-bit Windows is - the first parameter is transferred in RCX if it is an integer or in XMM0 if it is a float or double, second in RDX or XMM1, third in RB or XMM2 and fourth in R9 or in XMM9. No more than 4 parameters can be transferred in registers, ragardless of types. Any further parameters are transferred on the stack with the first parameter at the lowest address & aligned by 8. While in Linux, first six parameters are transferred in RDI, RSI, RDX, RCX, R8 & R9 resp.. The first 8 floating-point parameters are transferred in XMM0 - XMM7. All these registers can be used, so in maximum fourteen parameters can be transferred in registers. Any further parameters are transferred in stack with first parameter at the lowest address and aligned by 8. Note that 64-bit Linux does not use the same registers for parameters transfer as done by 64-bit Windows. Also, for 64-bit, the calling save status registers for Windows(SI, RDI, XMM6 - XMM15) are different form Linux which means that calling conventions are different for Windows and Linux for 64-bit. All compilers for 64-bit Windows use the COFF/PE32+ format, while compilers for Linux use ELF64 format. HIH. ~BR Mukkaysh Srivastav |
| July 2, 2009 9:16 PM PDT
gangti
|
I appreciate your answer of my question, and the whole knowledge of calling conventions. My problem is that I just want to modify the arguments from 5 to 9 as following code shows, for windows x64. Am I right or not? I want to get an official answer from intel to persuade my boss. So I am looking forward to you reply... ================================================== #undef NULL #define NULL 0 void main() { func(NULL,NULL,NULL,NULL,(char*)NULL, (char*)NULL,(char*)NULL,(char*)NULL,(char*)NULL); } void func(char *arg1, char *arg2, char *arg3, char *arg4, char *arg5, char *arg6, char *arg7, char *arg8, char *arg9) { // } ================================================== Though another way to approach this is to give an explictit prototype, I give the above approach first. It's a pity that I cannot change the approach now. |
| June 14, 2010 7:44 PM PDT
gangti
| the read/modify instructions both are MOV |
| November 12, 2010 11:53 PM PST
Todd Bezenek
|
Cool brain-teaser! -Todd |



gangti
==================================================
#undef NULL
#define NULL 0
void main() {
func(NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
}
void func(char *arg1, char *arg2, char *arg3,
char *arg4, char *arg5, char *arg6,
char *arg7, char *arg8, char *arg9)
{
//
}
==================================================
When I port above code from x86 to x64/ia64, I found:
A.
In windows x64 platform, when compiled with cl
(vs2005's x64 cross compiler)the first four param
will be transferred to func with universal registers,
using mov(same as movzx) assembly instructions, so
the upper 32 bits will be zeroed.
So in the above code, arg1~arg4 will be transferred
correctly, although arg5~arg9 may have problem.
But in linux x64 platform, when compiled with cc(gcc's
component), I found the first sixth parm will be
transferred with universal registers.
So in the above code, arg1~arg6 will be transferred
correctly, although arg7~arg9 may have problem.
B.
In windows ia64 platform, when compiled with cl
(vs2005's ia64 cross compiler)the first eight param
will be transferred to func with universal registers,
using mov(upper bits will be signed extended)assembly
instruction. In this case, that is 0 extended for the
upper bits. The other params will be put in the stack
memory, but they will be first put to registers, and
then stack memory.
So in the above code, arg1~arg4 will be transferred
correctly, although arg5~arg9 may have problem.
So the questions is, whether I am right or not?
May I rely on the knowledge of intermediate assembly
code, and just modify the code as follows for windows
x64??????
==================================================
#undef NULL
#define NULL 0
void main() {
func(NULL,NULL,NULL,NULL,(char*)NULL,
(char*)NULL,(char*)NULL,(char*)NULL,(char*)NULL);
}
void func(char *arg1, char *arg2, char *arg3,
char *arg4, char *arg5, char *arg6,
char *arg7, char *arg8, char *arg9)
{
//
}
==================================================
Another questions is that, why linux x64 put the first
six params to registers while windows x64 put the first
four params to registers????
The hardware(cpu especially) is the same, so why shouldn't
the calling conventions is the same????
--the end--