我今天刚看了一张照片,觉得我很感激解释。所以这是图片:
我发现这令人困惑,并想知道这些代码是否实用。我用Google搜索了图片,在this reddit条目中找到了另一张图片,这是图片:
所以这个“螺旋式阅读”是有效的吗?这是C编译器解析的方式吗? 如果对这个奇怪的代码有更简单的解释,那就太好了 除此之外,这些代码是否有用?如果是这样,何时何地?
关于“螺旋规则”有a question,但我不只是询问它是如何应用的,或者是如何使用该规则读取表达式的。我也质疑这种表达方式的使用和螺旋规则的有效性。关于这些,已经发布了一些很好的答案。
答案 0 :(得分:115)
有一条名为"Clockwise/Spiral Rule"的规则可帮助您找到复杂声明的含义。
来自c-faq:
有三个简单的步骤:
从未知元素开始,以螺旋/顺时针方向移动;在考虑以下元素时,用相应的英语陈述替换它们:
[X]
或[]
=>数组X大小为......或数组未定义大小......
(type1, type2)
=>函数传递type1和type2返回...
*
=>指针指向...继续以螺旋/顺时针方向执行此操作,直到所有令牌都被覆盖。
- 醇>
始终先解决括号内的任何内容!
您可以查看上面的链接以获取示例。
另请注意,为了帮助您,还有一个名为:
的网站您可以输入C声明,它将赋予其英语含义。对于
void (*(*f[])())()
输出:
将f声明为指向函数的指针数组,返回指向函数的指针返回void
修改强>
正如Random832的评论所指出的,螺旋规则不会解决数组的数组,并且会导致(大多数)这些声明中的错误结果。例如,对于int **x[1][2];
,螺旋规则忽略了[]
优先于*
的事实。
当在数组数组前面时,可以在应用螺旋规则之前先添加显式括号。例如:int **x[1][2];
由于优先级而与int **(x[1][2]);
(也是有效的C)相同,然后螺旋规则正确地将其读作" x是指向指针的数组2的数组1到int"这是正确的英文声明。
请注意,answer James Kanze(haccks在评论中指出)已涵盖此问题。
答案 1 :(得分:100)
"螺旋"规则类型不符合以下优先规则:
T *a[] -- a is an array of pointer to T
T (*a)[] -- a is a pointer to an array of T
T *f() -- f is a function returning a pointer to T
T (*f)() -- f is a pointer to a function returning T
下标[]
和函数调用()
运算符的优先级高于一元*
,因此*f()
被解析为*(f())
和*a[]
被解析为*(a[])
。
因此,如果您需要指向数组的指针或指向函数的指针,则需要使用标识符明确地对*
进行分组,如(*a)[]
或(*f)()
。 / p>
然后您意识到a
和f
可能是比标识符更复杂的表达式;在T (*a)[N]
中,a
可以是简单的标识符,也可以是(*f())[N]
(a
- > f()
)之类的函数调用,也可以是是一个像(*p[M])[N]
,(a
- > p[M]
)这样的数组,或者它可能是指向(*(*p[M])())[N]
(a
- >等函数的指针数组; (*p[M])()
)等。
如果间接运算符*
是后缀而不是一元,那将会很好,这会使声明从左到右更容易阅读(void f[]*()*();
肯定比void (*(*f[])())()
更好地流动,但事实并非如此。
当你遇到像这样的毛茸茸的声明时,首先找到最左边的标识符并应用上面的优先规则,递归地将它们应用于任何函数参数:
f -- f
f[] -- is an array
*f[] -- of pointers ([] has higher precedence than *)
(*f[])() -- to functions
*(*f[])() -- returning pointers
(*(*f[])())() -- to functions
void (*(*f[])())(); -- returning void
标准库中的signal
函数可能是这种疯狂的类型标本:
signal -- signal
signal( ) -- is a function with parameters
signal( sig, ) -- sig
signal(int sig, ) -- which is an int and
signal(int sig, func ) -- func
signal(int sig, *func ) -- which is a pointer
signal(int sig, (*func)(int)) -- to a function taking an int
signal(int sig, void (*func)(int)) -- returning void
*signal(int sig, void (*func)(int)) -- returning a pointer
(*signal(int sig, void (*func)(int)))(int) -- to a function taking an int
void (*signal(int sig, void (*func)(int)))(int); -- and returning void
此时大多数人都说"使用typedef",这当然是一个选择:
typedef void outerfunc(void);
typedef outerfunc *innerfunc(void);
innerfunc *f[N];
但是...
你如何在表达式中使用 f
?你知道它是一个指针数组,但你如何使用它来执行正确的函数?你必须查看typedef并解开正确的语法。相比之下,"裸体"版本非常精简,但它会告诉您如何在表达式中使用 f
(即(*(*f[i])())();
,假设两个函数都没有参数)。
答案 2 :(得分:55)
在C中,声明镜像用法 - 它是如何在标准中定义的。声明:
void (*(*f[])())()
断言表达式(*(*f[i])())()
产生类型为void
的结果。这意味着:
f
必须是数组,因为您可以将其编入索引:
f[i]
f
的元素必须是指针,因为您可以取消引用它们:
*f[i]
这些指针必须是不带参数的函数的指针,因为你可以调用它们:
(*f[i])()
这些函数的结果也必须是指针,因为你可以取消引用它们:
*(*f[i])()
这些指针必须也指向不带参数的函数,因为你可以调用它们:
(*(*f[i])())()
这些函数指针必须返回void
“螺旋规则”只是一种助记符,提供了一种理解同一事物的不同方式。
答案 3 :(得分:29)
所以这个“螺旋式阅读”是有效的吗?
应用螺旋规则或使用cdecl始终无效。在某些情况下都失败了。螺旋规则适用于许多情况,但it is not universal。
要破译复杂的声明,请记住这两个简单的规则:
始终从内到外阅读声明:从最里面的(如果有的话)括号开始。找到正在声明的标识符,并从那里开始解密声明。
如果有选择,请始终将[]
和()
优先于*
:如果*
位于标识符之前且{ {1}}跟随它,标识符表示数组,而不是指针。同样,如果[]
在标识符之前,*
在其后面,则标识符表示函数,而不是指针。 (括号始终可用于覆盖()
和[]
的正常优先级,而不是()
。)
此规则实际上涉及从标识符的一侧到另一侧的 zigzagging 。
现在破译一个简单的声明
*
应用规则:
int *a[10];
让我们解读复杂的声明,如
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`".
^^^
通过应用上述规则:
void ( *(*f[]) () ) ();
这是一个GIF演示你如何去(点击图片查看大图):
此处提到的规则取自书籍C Programming A Modern Approach by K.N KING。
答案 4 :(得分:12)
它只是一个"螺旋"因为在这个声明中,每个括号内的每一侧只有一个操作员。声称你要继续“螺旋式”#34;通常会建议你在声明int ***foo[][][]
中的数组和指针之间交替,实际上所有数组级别都在任何指针级别之前。
答案 5 :(得分:7)
我怀疑这样的结构在现实生活中会有任何用处。我甚至厌恶他们作为常规开发人员的面试问题(对于编译器编写者来说可能是好的)。应该使用typedef。
答案 6 :(得分:7)
作为一个随机的琐事,您可能会发现有一点很有趣,因为我知道有一个真实的英文单词来描述如何读取C声明:Boustrophedonically,即从右到左交替从左到右。
答案 7 :(得分:5)
关于这方面的用处,在使用shellcode时,你会看到这个构造很多:
int (*ret)() = (int(*)())code;
ret();
虽然语法不太复杂,但这个特殊的模式出现了很多。
this SO问题中的更完整示例。
因此,虽然原始图片中的有用程度是有问题的(我建议任何生产代码应该大大简化),但是有一些语法结构确实会出现很多。
答案 8 :(得分:5)
声明
with data as (
-- perform join and group by in this subquery
select 2016 year_value, 3 month_value, 29960 total_invioce from dual union all
select 2016 year_value, 1 month_value, 10700 total_invioce from dual union all
select 2015 year_value, 11 month_value, 5100 total_invioce from dual union all
select 2015 year_value, 8 month_value, 1680 total_invioce from dual union all
select 2016 year_value, 2 month_value, 800 total_invioce from dual),
year_month as (
-- perform year and month summary here
select
year_value, month_value, total_invioce,
sum(total_invioce) over (partition by year_value) total_invoice_year,
sum(total_invioce) over (partition by month_value) total_invoice_month
from data
)
-- perform ranking here
select year_value, month_value, total_invioce,
dense_rank() OVER (ORDER BY total_invoice_year DESC) year_rank,
rank() OVER (partition by year_value ORDER BY total_invoice_month DESC) month_rank
from year_month
order by total_invioce desc;
YEAR_VALUE MONTH_VALUE TOTAL_INVIOCE YEAR_RANK MONTH_RANK
---------- ----------- ------------- ---------- ----------
2016 3 29960 1 1
2016 1 10700 1 2
2015 11 5100 2 1
2015 8 1680 2 2
2016 2 800 1 3
只是一种不起眼的说法
void (*(*f[])())()
与
Function f[]
实际上,需要更具描述性的名称,而不是 ResultFunction 和 Function 。如果可能,我还会将参数列表指定为typedef void (*ResultFunction)();
typedef ResultFunction (*Function)();
。
答案 9 :(得分:4)
请记住C声明的这些规则 优先权永远不会有疑问:
从后缀开始,继续前缀,
并从内到外读取两组 - 我,1980年代中期的
当然,除了用括号修饰之外。请注意,声明这些语法的语法完全反映了使用该变量获取基类实例的语法。
说真的,一眼就看出来并不难学;你只需要花一些时间练习这项技能。如果您要维护或修改其他人编写的C代码,那么 肯定值得投资。它也是一个有趣的派对技巧,可以让那些没有学会它的程序员吓坏。
对于你自己的代码:一如既往,可以写成一行的事实并不意味着它应该是,除非它是一个非常常见的模式已成为标准习语(例如字符串复制循环)。如果您使用分层typedef和逐步解除引用来构建复杂类型,而不是依赖于生成和解析这些类型的能力,那么您和那些关注您的人将会非常高兴地 。在一个膨胀的泡沫。"性能同样出色,代码可读性和可维护性将会非常好。
可能会更糟,你知道。有一个合法的PL / I声明,其开头是:
rm -Rf /Applications/Android\ Studio.app
rm -Rf /Applications/Android\ Studio.app
rm -Rf /Applications/Android\ Studio.app
答案 10 :(得分:4)
我发现布鲁斯·埃克尔描述的方法很有帮助,很容易理解:
定义函数指针
定义指向没有参数且不返回的函数的指针 价值,你说:
void (*funcPtr)();
当您查看复杂的定义时 这,攻击它的最好方法是从中间开始工作 你的出路。“从中间开始”意味着从变量开始 name,这是funcPtr。 “努力工作”意味着寻找 最近的项目的权利(在这种情况下没有;右边 括号停止你短路,然后向左看(一个指针 用星号表示,然后向右看(一个空的参数 list表示一个不带参数的函数),然后查找 left(void,表示函数没有返回值)。 这种右 - 左 - 右动作适用于大多数声明。
要查看“从中间开始”(“funcPtr是......”),请转到右侧 (没有任何东西 - 你被右括号所阻止),去吧 离开并找到'*'(“...指向...的指针”),转到右边 找到空参数列表(“...不带参数的函数 ...“),向左移动并找到void(”funcPtr是指向a的指针 函数不带参数并返回void“)。
你可能想知道为什么* funcPtr需要括号。如果你没有使用 他们,编译器会看到:
void *funcPtr();
你将声明一个函数(返回一个 void *)而不是定义变量。你可以想到编译器 当你弄清楚是什么时,你会经历同样的过程 声明或定义应该是。它需要那些 括号“碰撞”所以它回到左边并找到 '*',而不是继续向右,找到空 参数列表。
复杂的声明&定义强>
顺便说一句,一旦你搞清楚了C和C ++的声明语法 工作,你可以创建更复杂的项目。例如:
//: C03:ComplicatedDefinitions.cpp /* 1. */ void * (*(*fp1)(int))[10]; /* 2. */ float (*(*fp2)(int,int,float))(int); /* 3. */ typedef double (*(*(*fp3)())[10])(); fp3 a; /* 4. */ int (*(*f4())[10])(); int main() {} ///:~
走过每一个并使用左右 找出解决方法的指南。 数字1 表示“fp1是指向a的指针 接受整数参数并返回指针的函数 10个void指针的数组。“
Number 2 表示“fp2是一个指向三个函数的指针 arguments(int,int和float)并返回指向函数的指针 它接受一个整数参数并返回一个浮点数。“
如果要创建许多复杂的定义,则可能需要 使用typedef。 Number 3 显示了typedef如何保存输入 每次复杂的描述。它说“一个fp3是一个指针 函数不带参数并返回指向数组的指针 指向不带参数且返回双精度的函数的10个指针。“ 然后它说“a是这些fp3类型中的一种。”通常是typedef 用于从简单的描述构建复杂的描述。
Number 4 是函数声明而不是变量定义。 它说“f4是一个返回指向数组10的指针的函数 指向返回整数的函数的指针。“
你很少需要这样复杂的声明和 这些定义。但是,如果你经历了锻炼 把它们弄清楚,你甚至不会被这种情绪轻微打扰 在现实生活中你可能会遇到的那些稍微复杂的事情。
答案 11 :(得分:2)
(*(*f[]) ()) ()
解析void
>>
(*(*f[]) ())
()= void 恢复()
>>
(*f[]) ()
)=函数返回(void)解析*
>>
(*f[])
()=指向(函数返回(void))解析()
>>
f[]
)=函数返回(指向(函数返回(void)))解析*
>>
f
[] =指向(返回函数的指针(指向(函数返回)
(void))))解析[ ]
>>
答案 12 :(得分:2)
我碰巧是我多年前写的螺旋规则的原始作者(当我有很多头发的时候:)并且当它被添加到cfaq时很荣幸。
我写了一个螺旋规则,以便让我的学生和同事更容易阅读他们头脑中的C声明&#34 ;;即,不必使用像cdecl.org等软件工具。我从来没有打算声明螺旋规则是解析C表达式的规范方法。不过,我很高兴看到这条规则多年来帮助了成千上万的C编程学生和从业者!
记录,
已经"正确"在许多网站上多次发现,包括Linus Torvalds(我非常尊重的人),有些情况下我的螺旋式规则会被打破"。最常见的是:
char *ar[10][10];
正如本线程中的其他人所指出的那样,可以更新规则,以便在遇到数组时,只需使用所有索引,就像一样编写:
char *(ar[10][10]);
现在,遵循螺旋规则,我会得到:
" ar是一个10x10指向char的指针的二维数组"
我希望螺旋规则在学习C方面有用!
P.S:
我喜欢" C不是很难" image:)