Should I place the parameter storage class specifier in the function definition or in both the declaration...












14















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?










share|improve this question

























  • What's the target compiler / system?

    – dbush
    Feb 13 at 16:12











  • Take a look here: port70.net/~nsz/c/c11/n1570.html#6.7.6.3 The only storage-class specifier that shall occur in a parameter declaration is register.

    – Eugene Sh.
    Feb 13 at 16:13






  • 1





    Storage class is not a factor in determining whether types are compatible. Therefore, the type of a function declared with register should be compatible with the type of a function declared without register, if they are otherwise compatible.

    – Eric Postpischil
    Feb 13 at 16:25






  • 2





    @EugeneSh. It's not an exception. Storage classes are never included in type information. auto int x; , static int x;, and register int x; all have type int.

    – PSkocik
    Feb 13 at 16:28








  • 1





    @PSkocik Why auto can't be included but register can (well, static would not make sense)?

    – Eugene Sh.
    Feb 13 at 16:31
















14















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?










share|improve this question

























  • What's the target compiler / system?

    – dbush
    Feb 13 at 16:12











  • Take a look here: port70.net/~nsz/c/c11/n1570.html#6.7.6.3 The only storage-class specifier that shall occur in a parameter declaration is register.

    – Eugene Sh.
    Feb 13 at 16:13






  • 1





    Storage class is not a factor in determining whether types are compatible. Therefore, the type of a function declared with register should be compatible with the type of a function declared without register, if they are otherwise compatible.

    – Eric Postpischil
    Feb 13 at 16:25






  • 2





    @EugeneSh. It's not an exception. Storage classes are never included in type information. auto int x; , static int x;, and register int x; all have type int.

    – PSkocik
    Feb 13 at 16:28








  • 1





    @PSkocik Why auto can't be included but register can (well, static would not make sense)?

    – Eugene Sh.
    Feb 13 at 16:31














14












14








14








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?










share|improve this question
















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?







c declaration definition c89






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Feb 13 at 21:54









John Conde

185k80372423




185k80372423










asked Feb 13 at 16:01









Carl Eric CodereCarl Eric Codere

1136




1136













  • What's the target compiler / system?

    – dbush
    Feb 13 at 16:12











  • Take a look here: port70.net/~nsz/c/c11/n1570.html#6.7.6.3 The only storage-class specifier that shall occur in a parameter declaration is register.

    – Eugene Sh.
    Feb 13 at 16:13






  • 1





    Storage class is not a factor in determining whether types are compatible. Therefore, the type of a function declared with register should be compatible with the type of a function declared without register, if they are otherwise compatible.

    – Eric Postpischil
    Feb 13 at 16:25






  • 2





    @EugeneSh. It's not an exception. Storage classes are never included in type information. auto int x; , static int x;, and register int x; all have type int.

    – PSkocik
    Feb 13 at 16:28








  • 1





    @PSkocik Why auto can't be included but register can (well, static would not make sense)?

    – Eugene Sh.
    Feb 13 at 16:31



















  • What's the target compiler / system?

    – dbush
    Feb 13 at 16:12











  • Take a look here: port70.net/~nsz/c/c11/n1570.html#6.7.6.3 The only storage-class specifier that shall occur in a parameter declaration is register.

    – Eugene Sh.
    Feb 13 at 16:13






  • 1





    Storage class is not a factor in determining whether types are compatible. Therefore, the type of a function declared with register should be compatible with the type of a function declared without register, if they are otherwise compatible.

    – Eric Postpischil
    Feb 13 at 16:25






  • 2





    @EugeneSh. It's not an exception. Storage classes are never included in type information. auto int x; , static int x;, and register int x; all have type int.

    – PSkocik
    Feb 13 at 16:28








  • 1





    @PSkocik Why auto can't be included but register can (well, static would not make sense)?

    – Eugene Sh.
    Feb 13 at 16:31

















What's the target compiler / system?

– dbush
Feb 13 at 16:12





What's the target compiler / system?

– dbush
Feb 13 at 16:12













Take a look here: port70.net/~nsz/c/c11/n1570.html#6.7.6.3 The only storage-class specifier that shall occur in a parameter declaration is register.

– Eugene Sh.
Feb 13 at 16:13





Take a look here: port70.net/~nsz/c/c11/n1570.html#6.7.6.3 The only storage-class specifier that shall occur in a parameter declaration is register.

– Eugene Sh.
Feb 13 at 16:13




1




1





Storage class is not a factor in determining whether types are compatible. Therefore, the type of a function declared with register should be compatible with the type of a function declared without register, if they are otherwise compatible.

– Eric Postpischil
Feb 13 at 16:25





Storage class is not a factor in determining whether types are compatible. Therefore, the type of a function declared with register should be compatible with the type of a function declared without register, if they are otherwise compatible.

– Eric Postpischil
Feb 13 at 16:25




2




2





@EugeneSh. It's not an exception. Storage classes are never included in type information. auto int x; , static int x;, and register int x; all have type int.

– PSkocik
Feb 13 at 16:28







@EugeneSh. It's not an exception. Storage classes are never included in type information. auto int x; , static int x;, and register int x; all have type int.

– PSkocik
Feb 13 at 16:28






1




1





@PSkocik Why auto can't be included but register can (well, static would not make sense)?

– Eugene Sh.
Feb 13 at 16:31





@PSkocik Why auto can't be included but register can (well, static would not make sense)?

– Eugene Sh.
Feb 13 at 16:31












5 Answers
5






active

oldest

votes


















15














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,




  1. The register keyword is a relic. Compilers are not obligated to make any attempt at all to actually assign register 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 the register keyword.


  2. 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.







share|improve this answer


























  • As replied above, the compiler i am working with is ANSI C89 compatible, and currently the code of it is K&R, so i am trying to "modernize" the code a bit... and then bootstrap it, it does use the register keyword into consideration in its code generator.

    – Carl Eric Codere
    Feb 14 at 14:33











  • I think, as with other advice given below,the safe bet is to have the register specifier in both the definition and declaration, and this is what I did, I just wanted to make sure it was a logical choice.

    – Carl Eric Codere
    Feb 14 at 14:35











  • @CarlEricCodere, it is important to understand what I said about register. The point is not that compilers necessarily don't or shouldn't pay attention to it, but rather that if if they do, then it is more likely to cause them to emit worse code than better. Compilers from the 90's and onward are much better at allocating data to registers effectively than 70s-era compilers were. They do not need the programmer's help in that regard, so generally the best case is that using the register keyword is a needless redundancy.

    – John Bollinger
    Feb 14 at 14:43













  • However, if you do retain the register keywords then yes, putting them in both declaration and definition is best and cleanest.

    – John Bollinger
    Feb 14 at 14:46



















6














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).






share|improve this answer


























  • Interesting observation... I guess this is compiler specific in that case.

    – Carl Eric Codere
    Feb 14 at 14:40











  • @CarlEricCodere I could be wrong but I don't think the standard has wording about how prototypes with different storage classifiers should be combined with the contradicting definition so testing is all there is left. For top level qualifiers (like the last const int int const* const X) I think it is prescribed that only those in the definition are used in the body. (They don't play a role in the function's type.) IRC last time I checked, __attributes (nonstandard extension) and things like inline/_Noreturn tended to accumulate.

    – PSkocik
    Feb 14 at 14:59





















6














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.






share|improve this answer


























  • From my understanding, unless i am completely wrong, the storage class specifier in the parameter just means that the variable associated with the parameter should be placed in a register (even after the start of the code executes in the routine), it does not necessarily indicate it will be called using register calling conventions, no?

    – Carl Eric Codere
    Feb 14 at 14:38











  • @CarlEricCodere The standard doesn't actually say anything on that topic - only that the keyword register is permissible in the parameter list

    – Govind Parmar
    Feb 14 at 14:41



















3














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





share|improve this answer

































    0














    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.






    share|improve this answer

























      Your Answer






      StackExchange.ifUsing("editor", function () {
      StackExchange.using("externalEditor", function () {
      StackExchange.using("snippets", function () {
      StackExchange.snippets.init();
      });
      });
      }, "code-snippets");

      StackExchange.ready(function() {
      var channelOptions = {
      tags: "".split(" "),
      id: "1"
      };
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function() {
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled) {
      StackExchange.using("snippets", function() {
      createEditor();
      });
      }
      else {
      createEditor();
      }
      });

      function createEditor() {
      StackExchange.prepareEditor({
      heartbeatType: 'answer',
      autoActivateHeartbeat: false,
      convertImagesToLinks: true,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: 10,
      bindNavPrevention: true,
      postfix: "",
      imageUploader: {
      brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
      contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
      allowUrls: true
      },
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      });


      }
      });














      draft saved

      draft discarded


















      StackExchange.ready(
      function () {
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54674465%2fshould-i-place-the-parameter-storage-class-specifier-in-the-function-definition%23new-answer', 'question_page');
      }
      );

      Post as a guest















      Required, but never shown

























      5 Answers
      5






      active

      oldest

      votes








      5 Answers
      5






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes









      15














      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,




      1. The register keyword is a relic. Compilers are not obligated to make any attempt at all to actually assign register 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 the register keyword.


      2. 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.







      share|improve this answer


























      • As replied above, the compiler i am working with is ANSI C89 compatible, and currently the code of it is K&R, so i am trying to "modernize" the code a bit... and then bootstrap it, it does use the register keyword into consideration in its code generator.

        – Carl Eric Codere
        Feb 14 at 14:33











      • I think, as with other advice given below,the safe bet is to have the register specifier in both the definition and declaration, and this is what I did, I just wanted to make sure it was a logical choice.

        – Carl Eric Codere
        Feb 14 at 14:35











      • @CarlEricCodere, it is important to understand what I said about register. The point is not that compilers necessarily don't or shouldn't pay attention to it, but rather that if if they do, then it is more likely to cause them to emit worse code than better. Compilers from the 90's and onward are much better at allocating data to registers effectively than 70s-era compilers were. They do not need the programmer's help in that regard, so generally the best case is that using the register keyword is a needless redundancy.

        – John Bollinger
        Feb 14 at 14:43













      • However, if you do retain the register keywords then yes, putting them in both declaration and definition is best and cleanest.

        – John Bollinger
        Feb 14 at 14:46
















      15














      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,




      1. The register keyword is a relic. Compilers are not obligated to make any attempt at all to actually assign register 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 the register keyword.


      2. 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.







      share|improve this answer


























      • As replied above, the compiler i am working with is ANSI C89 compatible, and currently the code of it is K&R, so i am trying to "modernize" the code a bit... and then bootstrap it, it does use the register keyword into consideration in its code generator.

        – Carl Eric Codere
        Feb 14 at 14:33











      • I think, as with other advice given below,the safe bet is to have the register specifier in both the definition and declaration, and this is what I did, I just wanted to make sure it was a logical choice.

        – Carl Eric Codere
        Feb 14 at 14:35











      • @CarlEricCodere, it is important to understand what I said about register. The point is not that compilers necessarily don't or shouldn't pay attention to it, but rather that if if they do, then it is more likely to cause them to emit worse code than better. Compilers from the 90's and onward are much better at allocating data to registers effectively than 70s-era compilers were. They do not need the programmer's help in that regard, so generally the best case is that using the register keyword is a needless redundancy.

        – John Bollinger
        Feb 14 at 14:43













      • However, if you do retain the register keywords then yes, putting them in both declaration and definition is best and cleanest.

        – John Bollinger
        Feb 14 at 14:46














      15












      15








      15







      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,




      1. The register keyword is a relic. Compilers are not obligated to make any attempt at all to actually assign register 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 the register keyword.


      2. 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.







      share|improve this answer















      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,




      1. The register keyword is a relic. Compilers are not obligated to make any attempt at all to actually assign register 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 the register keyword.


      2. 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.








      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited Feb 13 at 23:21

























      answered Feb 13 at 16:38









      John BollingerJohn Bollinger

      82.1k74277




      82.1k74277













      • As replied above, the compiler i am working with is ANSI C89 compatible, and currently the code of it is K&R, so i am trying to "modernize" the code a bit... and then bootstrap it, it does use the register keyword into consideration in its code generator.

        – Carl Eric Codere
        Feb 14 at 14:33











      • I think, as with other advice given below,the safe bet is to have the register specifier in both the definition and declaration, and this is what I did, I just wanted to make sure it was a logical choice.

        – Carl Eric Codere
        Feb 14 at 14:35











      • @CarlEricCodere, it is important to understand what I said about register. The point is not that compilers necessarily don't or shouldn't pay attention to it, but rather that if if they do, then it is more likely to cause them to emit worse code than better. Compilers from the 90's and onward are much better at allocating data to registers effectively than 70s-era compilers were. They do not need the programmer's help in that regard, so generally the best case is that using the register keyword is a needless redundancy.

        – John Bollinger
        Feb 14 at 14:43













      • However, if you do retain the register keywords then yes, putting them in both declaration and definition is best and cleanest.

        – John Bollinger
        Feb 14 at 14:46



















      • As replied above, the compiler i am working with is ANSI C89 compatible, and currently the code of it is K&R, so i am trying to "modernize" the code a bit... and then bootstrap it, it does use the register keyword into consideration in its code generator.

        – Carl Eric Codere
        Feb 14 at 14:33











      • I think, as with other advice given below,the safe bet is to have the register specifier in both the definition and declaration, and this is what I did, I just wanted to make sure it was a logical choice.

        – Carl Eric Codere
        Feb 14 at 14:35











      • @CarlEricCodere, it is important to understand what I said about register. The point is not that compilers necessarily don't or shouldn't pay attention to it, but rather that if if they do, then it is more likely to cause them to emit worse code than better. Compilers from the 90's and onward are much better at allocating data to registers effectively than 70s-era compilers were. They do not need the programmer's help in that regard, so generally the best case is that using the register keyword is a needless redundancy.

        – John Bollinger
        Feb 14 at 14:43













      • However, if you do retain the register keywords then yes, putting them in both declaration and definition is best and cleanest.

        – John Bollinger
        Feb 14 at 14:46

















      As replied above, the compiler i am working with is ANSI C89 compatible, and currently the code of it is K&R, so i am trying to "modernize" the code a bit... and then bootstrap it, it does use the register keyword into consideration in its code generator.

      – Carl Eric Codere
      Feb 14 at 14:33





      As replied above, the compiler i am working with is ANSI C89 compatible, and currently the code of it is K&R, so i am trying to "modernize" the code a bit... and then bootstrap it, it does use the register keyword into consideration in its code generator.

      – Carl Eric Codere
      Feb 14 at 14:33













      I think, as with other advice given below,the safe bet is to have the register specifier in both the definition and declaration, and this is what I did, I just wanted to make sure it was a logical choice.

      – Carl Eric Codere
      Feb 14 at 14:35





      I think, as with other advice given below,the safe bet is to have the register specifier in both the definition and declaration, and this is what I did, I just wanted to make sure it was a logical choice.

      – Carl Eric Codere
      Feb 14 at 14:35













      @CarlEricCodere, it is important to understand what I said about register. The point is not that compilers necessarily don't or shouldn't pay attention to it, but rather that if if they do, then it is more likely to cause them to emit worse code than better. Compilers from the 90's and onward are much better at allocating data to registers effectively than 70s-era compilers were. They do not need the programmer's help in that regard, so generally the best case is that using the register keyword is a needless redundancy.

      – John Bollinger
      Feb 14 at 14:43







      @CarlEricCodere, it is important to understand what I said about register. The point is not that compilers necessarily don't or shouldn't pay attention to it, but rather that if if they do, then it is more likely to cause them to emit worse code than better. Compilers from the 90's and onward are much better at allocating data to registers effectively than 70s-era compilers were. They do not need the programmer's help in that regard, so generally the best case is that using the register keyword is a needless redundancy.

      – John Bollinger
      Feb 14 at 14:43















      However, if you do retain the register keywords then yes, putting them in both declaration and definition is best and cleanest.

      – John Bollinger
      Feb 14 at 14:46





      However, if you do retain the register keywords then yes, putting them in both declaration and definition is best and cleanest.

      – John Bollinger
      Feb 14 at 14:46













      6














      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).






      share|improve this answer


























      • Interesting observation... I guess this is compiler specific in that case.

        – Carl Eric Codere
        Feb 14 at 14:40











      • @CarlEricCodere I could be wrong but I don't think the standard has wording about how prototypes with different storage classifiers should be combined with the contradicting definition so testing is all there is left. For top level qualifiers (like the last const int int const* const X) I think it is prescribed that only those in the definition are used in the body. (They don't play a role in the function's type.) IRC last time I checked, __attributes (nonstandard extension) and things like inline/_Noreturn tended to accumulate.

        – PSkocik
        Feb 14 at 14:59


















      6














      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).






      share|improve this answer


























      • Interesting observation... I guess this is compiler specific in that case.

        – Carl Eric Codere
        Feb 14 at 14:40











      • @CarlEricCodere I could be wrong but I don't think the standard has wording about how prototypes with different storage classifiers should be combined with the contradicting definition so testing is all there is left. For top level qualifiers (like the last const int int const* const X) I think it is prescribed that only those in the definition are used in the body. (They don't play a role in the function's type.) IRC last time I checked, __attributes (nonstandard extension) and things like inline/_Noreturn tended to accumulate.

        – PSkocik
        Feb 14 at 14:59
















      6












      6








      6







      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).






      share|improve this answer















      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).







      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited Feb 13 at 16:41

























      answered Feb 13 at 16:22









      PSkocikPSkocik

      33.8k65475




      33.8k65475













      • Interesting observation... I guess this is compiler specific in that case.

        – Carl Eric Codere
        Feb 14 at 14:40











      • @CarlEricCodere I could be wrong but I don't think the standard has wording about how prototypes with different storage classifiers should be combined with the contradicting definition so testing is all there is left. For top level qualifiers (like the last const int int const* const X) I think it is prescribed that only those in the definition are used in the body. (They don't play a role in the function's type.) IRC last time I checked, __attributes (nonstandard extension) and things like inline/_Noreturn tended to accumulate.

        – PSkocik
        Feb 14 at 14:59





















      • Interesting observation... I guess this is compiler specific in that case.

        – Carl Eric Codere
        Feb 14 at 14:40











      • @CarlEricCodere I could be wrong but I don't think the standard has wording about how prototypes with different storage classifiers should be combined with the contradicting definition so testing is all there is left. For top level qualifiers (like the last const int int const* const X) I think it is prescribed that only those in the definition are used in the body. (They don't play a role in the function's type.) IRC last time I checked, __attributes (nonstandard extension) and things like inline/_Noreturn tended to accumulate.

        – PSkocik
        Feb 14 at 14:59



















      Interesting observation... I guess this is compiler specific in that case.

      – Carl Eric Codere
      Feb 14 at 14:40





      Interesting observation... I guess this is compiler specific in that case.

      – Carl Eric Codere
      Feb 14 at 14:40













      @CarlEricCodere I could be wrong but I don't think the standard has wording about how prototypes with different storage classifiers should be combined with the contradicting definition so testing is all there is left. For top level qualifiers (like the last const int int const* const X) I think it is prescribed that only those in the definition are used in the body. (They don't play a role in the function's type.) IRC last time I checked, __attributes (nonstandard extension) and things like inline/_Noreturn tended to accumulate.

      – PSkocik
      Feb 14 at 14:59







      @CarlEricCodere I could be wrong but I don't think the standard has wording about how prototypes with different storage classifiers should be combined with the contradicting definition so testing is all there is left. For top level qualifiers (like the last const int int const* const X) I think it is prescribed that only those in the definition are used in the body. (They don't play a role in the function's type.) IRC last time I checked, __attributes (nonstandard extension) and things like inline/_Noreturn tended to accumulate.

      – PSkocik
      Feb 14 at 14:59













      6














      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.






      share|improve this answer


























      • From my understanding, unless i am completely wrong, the storage class specifier in the parameter just means that the variable associated with the parameter should be placed in a register (even after the start of the code executes in the routine), it does not necessarily indicate it will be called using register calling conventions, no?

        – Carl Eric Codere
        Feb 14 at 14:38











      • @CarlEricCodere The standard doesn't actually say anything on that topic - only that the keyword register is permissible in the parameter list

        – Govind Parmar
        Feb 14 at 14:41
















      6














      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.






      share|improve this answer


























      • From my understanding, unless i am completely wrong, the storage class specifier in the parameter just means that the variable associated with the parameter should be placed in a register (even after the start of the code executes in the routine), it does not necessarily indicate it will be called using register calling conventions, no?

        – Carl Eric Codere
        Feb 14 at 14:38











      • @CarlEricCodere The standard doesn't actually say anything on that topic - only that the keyword register is permissible in the parameter list

        – Govind Parmar
        Feb 14 at 14:41














      6












      6








      6







      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.






      share|improve this answer















      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.







      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited Feb 14 at 14:37

























      answered Feb 13 at 16:14









      Govind ParmarGovind Parmar

      11.1k53360




      11.1k53360













      • From my understanding, unless i am completely wrong, the storage class specifier in the parameter just means that the variable associated with the parameter should be placed in a register (even after the start of the code executes in the routine), it does not necessarily indicate it will be called using register calling conventions, no?

        – Carl Eric Codere
        Feb 14 at 14:38











      • @CarlEricCodere The standard doesn't actually say anything on that topic - only that the keyword register is permissible in the parameter list

        – Govind Parmar
        Feb 14 at 14:41



















      • From my understanding, unless i am completely wrong, the storage class specifier in the parameter just means that the variable associated with the parameter should be placed in a register (even after the start of the code executes in the routine), it does not necessarily indicate it will be called using register calling conventions, no?

        – Carl Eric Codere
        Feb 14 at 14:38











      • @CarlEricCodere The standard doesn't actually say anything on that topic - only that the keyword register is permissible in the parameter list

        – Govind Parmar
        Feb 14 at 14:41

















      From my understanding, unless i am completely wrong, the storage class specifier in the parameter just means that the variable associated with the parameter should be placed in a register (even after the start of the code executes in the routine), it does not necessarily indicate it will be called using register calling conventions, no?

      – Carl Eric Codere
      Feb 14 at 14:38





      From my understanding, unless i am completely wrong, the storage class specifier in the parameter just means that the variable associated with the parameter should be placed in a register (even after the start of the code executes in the routine), it does not necessarily indicate it will be called using register calling conventions, no?

      – Carl Eric Codere
      Feb 14 at 14:38













      @CarlEricCodere The standard doesn't actually say anything on that topic - only that the keyword register is permissible in the parameter list

      – Govind Parmar
      Feb 14 at 14:41





      @CarlEricCodere The standard doesn't actually say anything on that topic - only that the keyword register is permissible in the parameter list

      – Govind Parmar
      Feb 14 at 14:41











      3














      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





      share|improve this answer






























        3














        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





        share|improve this answer




























          3












          3








          3







          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





          share|improve this answer















          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






          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Feb 14 at 18:17

























          answered Feb 13 at 16:12









          JohnHJohnH

          2,318718




          2,318718























              0














              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.






              share|improve this answer






























                0














                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.






                share|improve this answer




























                  0












                  0








                  0







                  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.






                  share|improve this answer















                  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.







                  share|improve this answer














                  share|improve this answer



                  share|improve this answer








                  edited Feb 13 at 16:31

























                  answered Feb 13 at 16:25









                  Frankie_CFrankie_C

                  3,6051723




                  3,6051723






























                      draft saved

                      draft discarded




















































                      Thanks for contributing an answer to Stack Overflow!


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid



                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.


                      To learn more, see our tips on writing great answers.




                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function () {
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54674465%2fshould-i-place-the-parameter-storage-class-specifier-in-the-function-definition%23new-answer', 'question_page');
                      }
                      );

                      Post as a guest















                      Required, but never shown





















































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown

































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown







                      Popular posts from this blog

                      How to change which sound is reproduced for terminal bell?

                      Title Spacing in Bjornstrup Chapter, Removing Chapter Number From Contents

                      Can I use Tabulator js library in my java Spring + Thymeleaf project?