问题
I'm working on porting some old K&R code to ANSI C, so I'm writing missing function prototype declarations. A lot of the function definitions have parameters with the register storage class, but I'm not sure if the register storage class specifier can be omitted in the function prototype?
With and without the register storage class specific declaration, the code compiles correctly (I tried GCC, VC++ and Watcom C). I could not find any information in the ISO/ANSI C89 standard on what is the correct way to do - is it OK if I just put the register keyword in the function definition?
int add(register int x, register int y);
int add(register int x, register int y)
{
return x+y;
}
This also builds correctly:
int add(int x, int y);
int add(register int x, register int y)
{
return x+y;
}
I want to make sure that the register storage specifier is really taken into account, according to the standard (my target is to compile using a very old compiler where this storage class specifier is important). Are both OK and it's just a question of coding style, or not?
回答1:
The key provision is that every declaration of the function must specify a compatible type for it. That requires compatible return types, and, for declarations such as yours that contain parameter lists, compatible type for each pair of corresponding parameters.
The question then becomes whether storage-class specifiers differentiate types. They do not, though the standard says that indirectly, by omission of storage-class specifiers from its discussion of type derivation. The property specified by a storage-class specifier in an object's declaration is therefore separate from that object's type.
Moreover, C89 specifically says
The storage-class specifier in the declaration specifiers for a parameter declaration, if present, is ignored unless the declared parameter is one of the members of the parameter type list for a function definition.
(emphasis added). A function definition is a declaration accompanied by a function body, as opposed to a forward declaration, so your two codes have identical semantics.
With and without the register storage class specific declaration, the code compiles correctly (I tried gcc, VC++ and Watcom), I could not find any information in the ISO/ANSI C89 standard on what is the correct way to do, or is it ok if i just put the register keyword in the function definition?
Personally, I would be inclined to make each forward declaration identical to the declaration in the corresponding function definition. This is never wrong if the function definition itself is correct.
HOWEVER,
The
register
keyword is a relic. Compilers are not obligated to make any attempt at all to actually assignregister
variables to registers, and modern compilers are a lot better than humans at deciding how to assign variables to registers and otherwise to generate fast code anyway. As long as you're converting old code, I would take the opportunity to remove all appearances of theregister
keyword.C89 is obsolete. The latest version of the standard is C 2018; C 2011 is widely deployed; and C99 (also, technically, obsolete) is available almost everywhere. Perhaps there is a good reason for you to target C89, but you should strongly consider instead targeting C11 or C18, or at least C99.
回答2:
Empirically on gcc and clang, the register
storage class on function params
is behaving the same as top-level qualifiers on params: only the ones in the definition (not a previous prototype) count.
(as for top level qualifiers, they're also discarded when type compatibility is considered, i.e., void f(int);
and void f(int const);
are compatible prototypes, but storage classes aren't part of types so type compatibility isn't an issue with them in the first place)
From the point of view of a C programmer, the only observable upshot of register
in C is that the compiler will not let you take the address of the declared object.
When I do:
void f(int A, int register B);
void f(int register A, int B)
{
/*&A;*/ //doesn't compile => A does have register storage here
&B; //compiles => B doesn't have register storage here;
//the register from the previous prototype wasn't considered
}
then &B
compiles, but &A
doesn't, so only the qualifiers in the definition appear to count.
I think that if you do need those register
, your best bet is to use it consistently in both places (the register
in prototypes could theoretically modify how calls are made).
回答3:
The C89 standard does say this (§ 3.5.4.3 External definitions):
The only storage-class specifier that shall occur in a parameter declaration is
register
.
So, it would appear that while register
is permissible as a function parameter storage class specifier, I still believe that whether or not this is honored really depends on the architecture and calling convention for the function.
Since you mentioned Watcom and C89, I'm going to assume you're targeting x86-16. The typical calling conventions for x86-16 (pascal
, stdcall
, and cdecl
) all require parameters to be pushed on the stack, not in registers, so I doubt that the keyword would actually modify how the parameters are passed to the function at the call site.
Consider, you have the following function definition:
int __stdcall add2(register int x, register int y);
The function goes into the object file as _add2@4
per requirements for stdcall. The @4 indicates how many bytes to remove from the stack at function return. The ret imm16
(return to calling procedure and pop imm16 bytes from stack) instruction is used in this case.
add2
will then have the following ret
at the end:
ret 4
If 4 bytes were not pushed on the stack at the call site (i.e. because the parameters were actually in registers), your program now has a misaligned stack and crashes.
回答4:
Since you're using an old compiler for an odd platform, sometimes just looking at what the compiler does is more important than assuming it will comply perfectly with the C spec.
This means you want to run each variant of your example through the compiler, with the compiler option set to generate assembly instead of an executable. Look at the assembly, and see if you can tell if it is using registers or not each way. In gcc, this is the S
option; for example:
gcc myfile.c -S -o myfile.s
回答5:
From C17 standard, 6.7.1 Storage-class specifiers:
A declaration of an identifier for an object with storage-class specifier register suggests that access to the object be as fast as possible. The extent to which such suggestions are effective is implementation-defined.
Imply that the compiler will try, or not depending on compiler, to speed-up parameter access, but doesn't imply any modification to the calling convention (basically no modifications on calling side).
So it should be present in the function definition, but is insignificant in prototype.
来源:https://stackoverflow.com/questions/54674465/should-i-place-the-parameter-storage-class-specifier-in-the-function-definition