复杂的C声明


70

我只是在浏览Internet上的一些代码后发现:

float * (*(*foo())[SIZE][SIZE])()

我如何阅读此声明?是否有一组特定的规则来读取这种复杂的声明?


22



22
您会找到编写它的程序员,然后让他告诉您这是什么意思。然后,您告诉老板解雇他,并且坚持认为永远不要使用他编写的代码。
皮特·贝克尔

Answers:


119

我已经有一段时间没有这样做了!

开始,foo然后继续。

float * (*(*foo())[SIZE][SIZE])()

foo是一个没有参数的函数...

括号括起来,无法正确执行。往左走:

float * (*(* foo())[SIZE][SIZE])()

foo是一个不带参数的函数,不返回指针

不能再往左走,所以我们越过括号再往右走

float * (*(* foo())[SIZE][SIZE])() float * (*(* foo())[SIZE][SIZE])() float * (*(* foo())[SIZE][SIZE])()

foo是一个不带参数的函数,它返回一个指向SIZE数组的指针...

结束括号已到达,再次向左到达指针符号:

float * (*(* foo())[SIZE][SIZE])()

foo是一个不带参数的函数,它返回一个指向SIZE数组的指针。

再次左括号,所以我们越过它并再次右行:

float *( *(* foo())[SIZE][SIZE])() float *( *(* foo())[SIZE][SIZE])()

foo是一个不带参数的函数,它返回一个指向SIZE数组的指针。

并留到最后

float * ( *(* foo())[SIZE][SIZE])()

foo是一个不带参数的函数,不返回指向SIZE数组的指针。SIZE指针的数组。不带参数的函数,不返回指向浮点数的指针。


无论写什么,请教他使用typedef

// Function that returns a pointer to float
typedef float* PFloatFunc ();

// Array of pointers to PFloatFunc functions
typedef PFloatFunc* PFloatFuncArray2D[SIZE][SIZE];

// Function that returns a pointer to a PFloatFuncArray2D
PFloatFuncArray2D* foo();

57
+1代表“无论写了什么,
请教

3
请注意,“无参数”部分仅对C ++正确。对于C,它的意思是“未指定的参数列表”(但是它不能是可变参数函数,因为即使在C语言中,它们也必须具有完整的原型范围)。
Jonathan Leffler

100

标准规则:找到最左边的标识符,然后出路,记住[]()绑定到*

            foo                      -- foo
            foo()                    -- is a function
           *foo()                    -- returning a pointer
          (*foo())[SIZE]             -- to a SIZE-element array
          (*foo())[SIZE][SIZE]       -- of SIZE-element arrays
         *(*foo())[SIZE][SIZE]       -- of pointers
        (*(*foo())[SIZE][SIZE])()    -- to functions
      * (*(*foo())[SIZE][SIZE])()    -- returning pointers
float * (*(*foo())[SIZE][SIZE])();   -- to float

因此,假设您有一堆返回指针的函数float

float *quux();
float *bar();
float *bletch();
float *blurga();

假设您要将它们存储在2x2表中:

float *(*tab[SIZE][SIZE])() = {quux, bar, bletch, blurga};

tab是指向函数的指针的SIZE x SIZE数组,返回指向的指针float

现在让我们决定我们要一个函数返回一个指向该表的指针:

float *(*(*foo())[SIZE][SIZE])()
{
  static float *(*tab[SIZE][SIZE])() = {quux, bar, bletch, blurga};
  return &tab;
}

请注意,您可能有几个函数可以构建具有不同功能的表,或者以不同的方式组织相同的功能:

float *(*(*qwerbl())[SIZE][SIZE])()
{
  static float *(*tab[SIZE][SIZE])() = {blurga, bletch, bar, quux};
  return tab;
}

这是我可以想到的唯一原因。您不应该经常在野外看到这样的类型(尽管它们确实偶尔出现,并且我犯了写类似令人发指的东西的罪行)。


1
qwerbl?您几乎用完了通用变量名称,不是吗:-) +1的理由。而且我敢肯定,“高度相关”的类型经常出现,但通常也涉及结构或类,这使得命名问题自然而然地消失了,就像在引入一些typedef时那样。
科斯,

@科斯:是的。还没有咖啡因的RDA,无法提出更好的建议。
约翰·博德

1
Wikipedia列出了一个元语法变量,因此您不会用完:foo,bar,baz,qux,quux,corge,grault,garply,waldo,fred,plugh,xyzzy,thud。
Waleed Khan

1
@WaleedKhan Dig通过引用进行搜索,您将最终进入The Jargon File。catb.org/jargon/html/M/metasyntactic-variable.html
科斯

6

根据cdecl.org

将foo声明为函数,返回指向数组SIZE的指针,数组的指向SIZE的指针返回给float的函数的指针

如果要手动解码,请使用Luchian Grigore给出的螺旋法则。


4

最好的做法是将其转换为一系列的typedef。

typedef float * fnReturningPointerToFloat();
typedef fnReturningPointerToFloat* fnArray[SIZE][SIZE];
fnArray* foo();

2
我花了超过一分钟的时间来做到这一点。
QuentinUK

3

通常,您可以尝试cdecl.org,但需要替换为SIZE

假设您将其交换SIZE为12,您将获得:

将foo声明为函数,该函数返回指向数组12的指针的指针,该指针指向函数的数组12

我不确定这是否对您有帮助!

这里有两个观察结果:

  1. 我猜想这段代码旁边没有注释,不能解释它的目的(即,不是对它的技术解释,而是从功能/业务的角度来实现的)程序员是否需要使用这样复杂的事情,他们应该足以向未来的维护者解释它的作用。
  2. 当然,在C ++中,实现同一件事的方式更加明显,而且可能更安全。

3
这是由于“ SIZE”所致,您必须改用文字(并在之后用常量自己替换)。
Clement J.

用一些数字代替SIZE !!
坎蒂亚

2

该文档为我提供了有关如何轻松准备任何C声明的最佳线索:

http://c-faq.com/decl/spiral.anderson.html

遵循三个简单步骤:

  • 从未知元素开始,沿螺旋/顺时针方向移动;在遇到以下要素时,将其替换为相应的英文陈述:

    • [X][] =>数组X大小为...或数组未定义大小为...

    • (type1, type2) =>函数传递type1和type2返回...

    • * =>指向...的指针

  • 继续以螺旋/顺时针方向执行此操作,直到所有令牌都被覆盖。

  • 始终首先用括号解决任何问题!

范例:

             +-------+
             | +-+   |
             | ^ |   |
        char *str[10];
         ^   ^   |   |
         |   +---+   |
         +-----------+

Question we ask ourselves: What is str?

``str is an...

- We move in a spiral clockwise direction starting with `str' and the first character we see is a `[' so, that means we have an array, so...
  ``str is an array 10 of...

- Continue in a spiral clockwise direction, and the next thing we encounter is the `*' so, that means we have pointers, so...
  ``str is an array 10 of pointers to...

- Continue in a spiral direction and we see the end of the line (the `;'), so keep going and we get to the type `char', so...
``str is an array 10 of pointers to char''

We have now ``visited'' every token; therefore we are done!

2

尽管上面的大多数答案都足够好,但是仍然缺少用于解码复杂C声明的完整规则集。我在下面提供了一套完整的程序来解码任何复杂的C声明。这套规则实际上是基于运算符的优先级。可以将诸如右手螺旋规则之类的规则视为这些规则集的捷径。

首先,我们需要了解一些对声明进行解码的知识。

声明的“基本类型”

AC声明始终只有一种基本声明类型。这是声明的最左侧。例如 -

  • int a -基本类型为“ int”
  • float *p -基本类型为“浮动”
  • char (*p)[3] -基本类型为“字符”

优先性和关联性

接下来,我们需要知道的优先顺序()[]以及*-引用操作

  1. ()[]-关联从左到右
  2. * -关联性从右到左

对应于上述每个运算符的短语

接下来,我们需要知道与每个运算符相对应的解码短语。前面的示例将阐明这一点。

  • () -函数返回
  • [SIZE] -SIZE数组
  • * -指向

现在按照以下规则解码声明

  1. 始终先写变量名,后跟“ is”。

    例如 -

  • int a-...
  • float *p- p是...
  • char (*p)[3]- p是...
  1. 总是以基本类型结尾

    例如 -

  • int a-一个是... INT
  • float *p- p是...浮动
  • char (*p)[3]- p是...焦炭
  1. 现在,使用以下子步骤填充这部分内容

    • 从名称开始,按照运算符的优先级和关联性选择下一个优先级最高的运算符,并将与之相对应的短语附加到解码字符串的中间部分。

    • 对剩余的声明重复上述子步骤,直到解码过程完成

注意1: 为简单起见,我忽略了函数的参数,但是可以将其包含在与相对应的短语之后()

注2:()与任何算术表达式一样, Parenthesis()更改运算符的优先级顺序。

注意3: 您可以在解码的声明中使用括号来增加可读性(我在下面的一些示例中已经做到了)。将()的每组视为一个单元。

注4: n维数组实际上是...(n-1次)array的数组。对于ex-int A [2] [3]-A是2的数组(3 int的数组),即A是2个元素的数组,其中每个元素是包含3个整数的数组

例子

  • int a- 一个int
  • float *p- p是指针浮
  • char (*p)[3] -p是指向3个字符的数组的指针

一些复杂的声明示例

  1. int **p[10] -p是10指向int的指针的数组
  2. int (*p)[10] -p是10整数数组的指针
  3. int *p(char *a) -p是返回int指针的函数
  4. int (*p(char*a))[10] -p是函数返回(指向(10个整数的数组)的指针)
  5. int *(*p)() -p是指向(函数返回(指向int的指针))的指针
  6. int (*p()[20])[10] -p是返回的函数(数组为20(指向(数组为10 int的指针)))

这组规则也可以使用const-const限定符将其左侧的术语(如果存在)修改,否则将其右侧的术语修改。

  1. const int *p[10] -p是指向int const的10指针的数组
  2. int const *p[10] -p是指向const int的10指针的数组(与第7个示例相同)
  3. int *const p[10] -p是10个指向int的const指针的数组

现在,一个非常复杂的示例在实践中不会在任何地方使用,但是仍然可以用来演示解码过程

  1. char *(*(**foo[][8])())[] -foo是(8的数组(指向(函数返回的指针(指向(char的指针的数组))的指针))))的数组)

现在最后解码问题中给出的声明

float * (*(*foo())[SIZE][SIZE])() -foo是函数返回(指向(SIZE数组(SIZE数组(指向(函数返回指向float的指针))的指针))的指针))

以下是我从中阅读此解码过程的文章的链接

示例10摘自本文

http://www.unixwiz.net/techtips/reading-cdecl.html


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.