C isn't that hard: void ( *( *f[] ) () ) ()

前端 未结 13 694
名媛妹妹
名媛妹妹 2020-11-29 14:25

I just saw a picture today and think I\'d appreciate explanations. So here is the picture:

I found this confusing and wondered if such codes are ever prac

相关标签:
13条回答
  • 2020-11-29 15:22

    The declaration

    void (*(*f[])())()
    

    is just an obscure way of saying

    Function f[]
    

    with

    typedef void (*ResultFunction)();
    
    typedef ResultFunction (*Function)();
    

    In practice, more descriptive names will be needed instead of ResultFunction and Function. If possible I would also specify the parameter lists as void.

    0 讨论(0)
  • 2020-11-29 15:22
    • void (*(*f[]) ()) ()

    Resolving void >>

    • (*(*f[]) ()) () = void

    Resoiving () >>

    • (*(*f[]) ()) = function returning (void)

    Resolving * >>

    • (*f[]) () = pointer to (function returning (void) )

    Resolving () >>

    • (*f[]) = function returning (pointer to (function returning (void) ))

    Resolving * >>

    • f[] = pointer to (function returning (pointer to (function returning (void) )))

    Resolving [ ] >>

    • f = array of (pointer to (function returning (pointer to (function returning (void) ))))
    0 讨论(0)
  • 2020-11-29 15:24

    So this "reading spirally" is something valid?

    Applying spiral rule or using cdecl are not valid always. Both fails in some cases. Spiral rule works for many cases, but it is not universal.

    To decipher complex declarations remember these two simple rules:

    • Always read declarations from inside out: Start from innermost, if any, parenthesis. Locate the identifier that's being declared, and start deciphering the declaration from there.

    • When there is a choice, always favour [] and () over *: If * precedes the identifier and [] follows it, the identifier represents an array, not a pointer. Likewise, if * precedes the identifier and () follows it, the identifier represents a function, not a pointer. (Parentheses can always be used to override the normal priority of [] and () over *.)

    This rule actually involves zigzagging from one side of the identifier to the other.

    Now deciphering a simple declaration

    int *a[10];
    

    Applying rule:

    int *a[10];      "a is"  
         ^  
    
    int *a[10];      "a is an array"  
          ^^^^ 
    
    int *a[10];      "a is an array of pointers"
        ^
    
    int *a[10];      "a is an array of pointers to `int`".  
    ^^^      
    

    Let's decipher the complex declaration like

    void ( *(*f[]) () ) ();  
    

    by applying the above rules:

    void ( *(*f[]) () ) ();        "f is"  
              ^  
    
    void ( *(*f[]) () ) ();        "f is an array"  
               ^^ 
    
    void ( *(*f[]) () ) ();        "f is an array of pointers" 
             ^    
    
    void ( *(*f[]) () ) ();        "f is an array of pointers to function"   
                   ^^     
    
    void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer"
           ^   
    
    void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer to function" 
                        ^^    
    
    void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer to function returning `void`"  
    ^^^^
    

    Here is a GIF demonstrating how you go (click on image for larger view):


    The rules mentioned here is taken from the book C Programming A Modern Approach by K.N KING.

    0 讨论(0)
  • 2020-11-29 15:26

    There is a rule called the "Clockwise/Spiral Rule" to help find the meaning of a complex declaration.

    From c-faq:

    There are three simple steps to follow:

    1. Starting with the unknown element, move in a spiral/clockwise direction; when ecountering the following elements replace them with the corresponding english statements:

      [X] or []
      => Array X size of... or Array undefined size of...

      (type1, type2)
      => function passing type1 and type2 returning...

      *
      => pointer(s) to...

    2. Keep doing this in a spiral/clockwise direction until all tokens have been covered.

    3. Always resolve anything in parenthesis first!

    You can check the link above for examples.

    Also note that to help you there is also a website called:

    http://www.cdecl.org

    You can enter a C declaration and it will give its english meaning. For

    void (*(*f[])())()
    

    it outputs:

    declare f as array of pointer to function returning pointer to function returning void

    EDIT:

    As pointed out in the comments by Random832, the spiral rule does not address array of arrays and will lead to a wrong result in (most of) those declarations. For example for int **x[1][2]; the spiral rule ignores the fact that [] has higher precedence over *.

    When in front of array of arrays, one can first add explicit parentheses before applying the spiral rule. For example: int **x[1][2]; is the same as int **(x[1][2]); (also valid C) due to precedence and the spiral rule then correctly reads it as "x is an array 1 of array 2 of pointer to pointer to int" which is the correct english declaration.

    Note that this issue has also been covered in this answer by James Kanze (pointed out by haccks in the comments).

    0 讨论(0)
  • 2020-11-29 15:30

    I doubt constructions like this can have any use in real life. I even detest them as interview questions for the regular developers (likely OK for compiler writers). typedefs should be used instead.

    0 讨论(0)
  • 2020-11-29 15:31

    Remember these rules for C declares
    And precedence never will be in doubt:
    Start with the suffix, proceed with the prefix,
    And read both sets from the inside, out.
    -- me, mid-1980's

    Except as modified by parentheses, of course. And note that the syntax for declaring these exactly mirrors the syntax for using that variable to get an instance of the base class.

    Seriously, this isn't hard to learn to do at a glance; you just have to be willing to spend some time practising the skill. If you're going to maintain or adapt C code written by other people, it's definitely worth investing that time. It's also a fun party trick for freaking out other programmers who haven't learned it.

    For your own code: as always, the fact that something can be written as a one-liner does't mean it should be, unless it is an extremely common pattern that has become a standard idiom (such as the string-copy loop). You, and those who follow you, will be much happier if you build complex types out of layered typedefs and step-by-step dereferences rather than relying on your ability to generate and parse these "at one swell foop." Performance will be just as good, and code readability and maintainability will be tremendously better.

    It could be worse, you know. There was a legal PL/I statement that started with something like:

    if if if = then then then = else else else = if then ...
    
    0 讨论(0)
提交回复
热议问题