我今天刚看了一张照片,觉得我很感激解释.所以这是图片:
我发现这令人困惑,并想知道这些代码是否实用.我用Google搜索了这张照片,并在此 reddit条目中找到了另一张照片,这是照片:
那么"螺旋式阅读"是有效的吗?这是C编译器解析的方式吗?
如果对这个奇怪的代码有更简单的解释,那就太好了.
除此之外,这些代码是否有用?如果是这样,何时何地?
有一个关于"螺旋规则"的问题,但我不只是询问它是如何应用的,或者是如何用该规则读取表达式的.我也质疑这种表达方式的使用和螺旋规则的有效性.关于这些,已经发布了一些很好的答案.
有一个称为"顺时针/螺旋规则"的规则来帮助找到复杂声明的含义.
来自c-faq:
有三个简单的步骤:
从未知元素开始,以螺旋/顺时针方向移动; 在制定以下元素时,用相应的英语声明替换它们:
[X]
或[]
=>数组X大小...或数组未定义大小...
(type1, type2)
=>函数传递type1和type2返回...
*
=>指针到......继续以螺旋/顺时针方向执行此操作,直到所有令牌都被覆盖.
始终先解决括号中的任何内容!
您可以查看上面的链接以获取示例.
另请注意,为了帮助您,还有一个名为的网站:
http://www.cdecl.org
您可以输入C声明,它将赋予其英语含义.对于
void (*(*f[])())()
它输出:
声明f为指向函数的指针数组,返回指向函数返回void的指针
编辑:
正如Random832的评论中所指出的,螺旋规则不会解决数组数组,并且会导致(大多数)这些声明中的错误结果.例如,int **x[1][2];
螺旋规则忽略了[]
优先级高的事实*
.
当在数组数组前面时,可以在应用螺旋规则之前首先添加显式括号.例如:由于优先级而与(也是有效的C)int **x[1][2];
相同,int **(x[1][2]);
然后螺旋规则正确地将其读作"x是指向int的指针的数组2的数组1",这是正确的英语声明.
请注意,这个问题也被包括在本答案由詹姆斯·甘孜(中指出haccks在评论).
"螺旋"规则类型不符合以下优先规则:
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)()
.
然后你意识到这一点,a
并且f
可以是比标识符更复杂的表达式; in 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
此时大多数人都说"使用typedefs",这当然是一个选择:
typedef void outerfunc(void); typedef outerfunc *innerfunc(void); innerfunc *f[N];
但...
你会如何在表达中使用 f
?你知道它是一个指针数组,但你如何使用它来执行正确的函数?你必须查看typedef并解开正确的语法.相比之下,"裸"版本非常引人注目,但它确切地告诉你如何在表达式中使用 f
(即(*(*f[i])())();
,假设两个函数都没有参数).
在C中,声明镜像用法 - 它是如何在标准中定义的.声明:
void (*(*f[])())()
断言表达式(*(*f[i])())()
产生类型的结果void
.意思是:
f
必须是一个数组,因为你可以索引它:
f[i]
f
必须是指针的元素,因为你可以取消引用它们:
*f[i]
那些指针必须是不带参数的函数的指针,因为你可以调用它们:
(*f[i])()
这些函数的结果也必须是指针,因为你可以取消引用它们:
*(*f[i])()
那些指针也必须是不带参数的函数的指针,因为你可以调用它们:
(*(*f[i])())()
那些函数指针必须返回 void
"螺旋规则"只是一种助记符,提供了一种理解同一事物的不同方式.
那么"螺旋式阅读"是有效的吗?
应用螺旋规则或使用cdecl始终无效.在某些情况下都失败了.螺旋规则适用于许多情况,但它并不普遍.
要破译复杂的声明,请记住这两个简单的规则:
始终从内到外阅读声明:从最里面的(如果有的话)括号开始.找到正在声明的标识符,并从那里开始解密声明.
当有选择时,总是喜欢[]
并且()
结束*
:如果*
在标识符之前并且在标识符[]
之后,则标识符表示数组,而不是指针.同样,如果*
在标识符之前并且在标识符()
之后,则标识符表示函数,而不是指针.(括号可以始终用于覆盖普通优先级[]
及()
以上*
.)
该规则实际上涉及从标识符的一侧到另一侧的曲折.
现在破译一个简单的声明
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[]) () ) ();
通过适用上述规则:
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`" ^^^^
这是一个GIF演示你如何去(点击图片查看大图):
这里提到的规则来自KN KING的C Programming A Modern Approach一书.
它只是一个"螺旋",因为在这个声明中,每个括号内的每一侧只有一个算子.声称你以"螺旋形式"进行通常会建议你在声明中的数组和指针之间交替,而int ***foo[][][]
实际上所有的数组级别都在任何指针级别之前.
我怀疑像这样的结构在现实生活中会有任何用处.我甚至厌恶他们作为常规开发人员的面试问题(对于编译器编写者来说可能是好的).应该使用typedef.
作为一个随机的琐事,你可能会发现在英语中有一个实际的词来描述如何读取C声明是有趣的:Boustrophedonically,即从右到左,从左到右交替.
参考文献:范德林登,1994年 - 第76页
关于这个的用处,在使用shellcode时你会看到这个构造很多:
int (*ret)() = (int(*)())code; ret();
虽然在语法上并不复杂,但这种特殊模式出现了很多.
在这个 SO问题中有更完整的例子.
因此,虽然原始图片中的有用性是有问题的(我建议任何生产代码应该大大简化),但是有一些语法结构确实出现了很多.
声明
void (*(*f[])())()
只是一种晦涩难懂的说法
Function f[]
同
typedef void (*ResultFunction)(); typedef ResultFunction (*Function)();
实际上,需要更多描述性名称而不是ResultFunction和Function.如果可能的话,我也会将参数列表指定为void
.