发布我最有争议的一个回答后在这里 ,我不敢问几个问题,并最终在我的知识填补一些空白。
为什么不是那种的表达((type_t *) x)
被认为是有效的左值,假设x
本身就是一个指针和一个左值,而不仅仅是一些表达?
我知道很多人会说“标准不允许它”,但是从逻辑的角度来看它似乎是合理的。 什么是标准不允许它的原因是什么? 毕竟,任何两个指针具有相同的尺寸和指针类型仅仅是一个编译时的抽象,其指示应该在做指针运算时所施加的合适的偏移。
发布我最有争议的一个回答后在这里 ,我不敢问几个问题,并最终在我的知识填补一些空白。
为什么不是那种的表达((type_t *) x)
被认为是有效的左值,假设x
本身就是一个指针和一个左值,而不仅仅是一些表达?
我知道很多人会说“标准不允许它”,但是从逻辑的角度来看它似乎是合理的。 什么是标准不允许它的原因是什么? 毕竟,任何两个指针具有相同的尺寸和指针类型仅仅是一个编译时的抽象,其指示应该在做指针运算时所施加的合适的偏移。
一个更好的实例中,一元+
产生一个右值,如同x+0
。
其根本原因是,所有这些东西,包括你的施法,创造新的价值。 铸造一个值,它已经是类型,也创造了新的价值,更不用说指向不同类型是否具有相同的表示或不。 在某些情况下,新价值恰好等于旧值,但原则上这是一个新的价值,它并不打算用作旧对象的引用,这就是为什么它是一个右值。
对于这些是左值,标准必须添加的,而不是一个新的价值,在旧对象的引用上一个左值结果中,当一些操作一些特殊情况。 AFAIK有那些特殊情况没有很大的需求。
铸造的结果是从不通过自己的左值。 但*((type_t *) x)
是一个左值。
因为在一般投表达式不产生左值。
那么,铸件进行类型转换。 在一般的情况下类型转换为一个非平凡操作,这完全改变了值的表示。 在这种情况下,它应该是非常明显,任何转换的结果是不可能是一个左值。
例如,如果有一个int i = 0;
变量,你可以将其转换为类型double
的(double) i
。 你怎么可能指望这种转换的结果是左值? 我的意思是,它只是没有任何意义。 你显然希望能够做到(double) i = 3.0;
...或者double *p = &(double) i;
那么,究竟应该发生的值, i
在前者的例子,考虑式double
甚至可能不会有大小类型相同int
? 而且,即使他们有相同的大小,你会期待发生什么呢?
您对具有相同尺寸的所有指针假设是不正确。 在一般情况下,C语言(除少数例外)不同的指针类型有不同的尺寸,不同的内部表示和不同的对齐要求。 即使他们保证具有相同的表示,我仍然不明白为什么指针应该从所有其它类型的分离,并给出了明确的转换情况特殊处理。
最后,你似乎在这里提倡的是,你的初始转换应执行一个指针类型的原始存储重新解释作为另一个指针类型。 在几乎所有情况下的原始记忆的重新解释是一个黑客。 为什么这个技巧应该被提升到了语言特征的水平完全是我不清楚。
由于这是一个黑客,执行这样的重新解释应要求用户有意识地努力。 如果你想在你的榜样执行它,你应该做的*(type_t **) &x
,这的确会重新解释x
类型的左值type_t *
。 但允许通过一个单纯的同样的事情(type_t *) x
将是一场灾难与C语言的设计原理完全断开。
C标准被写入支持需要怪异的黑客来实现对所有指向的类型的C指针模型异国情调的机器架构。 为了允许编译器使用的最有效的指针表示为每个指向的键入,C标准并不要求不同的指针表示是兼容的。 在这样一个异国建筑空隙指针类型必须使用最一般的和最慢因而不同表示的。 有在C FAQ等现在已经过时的架构的一些具体的例子: http://c-faq.com/null/machexamp.html
请注意,如果x
是一个指针类型, *(type_t **)&x
是一个左值。 然而,对其进行访问这样也许除了在非常有限的情况下,调用未定义的行为因违反走样。 它可能是合法的唯一情况是,如果指针类型对应符号/无符号或空隙/字符指针类型,但即使如此,我怀疑。
(type_t *)x
不是左值,因为(T)x
是从未左值,无论其类型T
或表达x
。 (type_t *)
是一个特例(T)
从最顶层,它一般会起不到任何作用。 而不是“((type_t *)x)=”一个还不如直接做“X =”,假设x是你的榜样的指针。 如果一个人希望直接修改由地址“X”,但在同一时间指出的值解释它是一个指向一个新的数据类型,然后*((type_t **)&X)=之后是前进的方向。 再次((type_t **)&X)=将没有任何意义,何况事实上,它是不是一个有效的左值。
另外,在((INT *)X)++,其中至少“GCC”不沿“左值”的线抱怨的情况下,它可以被重新解释为“X =(INT *)X + 1”
其实你是对的,错在同一时间。
在C语言中有安全强制转换任何左值到左值任何的能力。 但是,语法是比你直接的方法不同的一点:
左值的指针可以被浇铸左值在 C 不同类型的这样的指针 :
char *ptr;
ptr = malloc(20);
assert(ptr);
*(*(int **)&ptr)++ = 5;
如malloc()
需要满足所有的对准要求,这也是可以接受的使用。 然而,以下是不可移植的,并可能导致一个例外,由于在某些机器上错排列:
char *ptr;
ptr = malloc(20);
assert(ptr);
*ptr++ = 0;
*(*(int **)&ptr)++ = 5; /* can throw an exception due to misalignment */
把它们加起来:
*
上的指针导致左值( *ptr
可以分配给)。 ++
(像*(arg)++
)需要一个左值来进行操作( arg
必须是一个左值) 因此((int *)ptr)++
失败,因为ptr
是一个左值,但(int *)ptr
不是。 该++
可以改写为((int *)ptr += 1, ptr-1)
和它的(int *)ptr += 1
,其由于失败导致纯右值铸造。
请注意,这是不是一种语言的缺点。 铸造不能产生左值。 请看下面:
(double *)1 = 0;
(double)ptr = 0;
(double)1 = 0;
(double *)ptr = 0;
第3不进行编译。 为什么会有人想到4号线编译? 编程语言不应该揭露这种令人惊讶的行为。 更有甚者,这可能会导致程序的一些不明确的行为。 考虑:
#ifndef DATA
#define DATA double
#endif
#define DATA_CAST(X) ((DATA)(X))
DATA_CAST(ptr) = 3;
这不能编译,对不对? 但是,如果你的期望helds,这突然编译cc -DDATA='double *'
! 从一个稳定点它不引入了对某些类型转换这样的上下文左值是重要的。
对于C正确的做法是,无论是将左值或者有没有,这不取决于这可能是令人惊讶的一些任意内容。
作为由延斯指出已经有一个操作符来创建左值。 它的指针引用运营商的“一元*
”(如*ptr
)。
注意, *ptr
可以写为0[ptr]
和*ptr++
可被写为0[ptr++]
数组下标是左值,因此*ptr
是一个左值了。
等等,什么? 0[ptr]
必须是一个错误,是吗?
其实,没有。 试试吧! 这是有效的C.下面的C程序有效,在各方面英特尔32/64位,所以它编译和运行成功:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int
main()
{
char *ptr;
ptr = malloc(20);
assert(ptr);
0[(*(int **)&ptr)++] = 5;
assert(ptr[-1]==0 && ptr[-2]==0 && ptr[-3]==0 && ptr[-4]==5);
return 0;
}
在C,我们能够两者。 管型,从未创建左值。 并使用的能力投下的方式,我们可以保持左值属性活着。
但是为了得到一个左出铸造,需要两个步骤:
因此,而不是错误的*((int *)ptr)++
,我们可以写*(*(int **)&ptr)++
。 这也可以确保,即ptr
在这个表达式必须是一个左值了。 或与C预处理器的帮助下写:
#define LVALUE_CAST(TYPE,PTR) (*((TYPE *)&(PTR)))
因此,对于任何传入void *ptr
(这可能会伪装成char *ptr
),我们可以这样写:
*LVALUE_CAST(int *,ptr)++ = 5;
除了通常的指针运算警告(程序异常终止或不兼容的类型,其主要来自aligment问题茎未定义的行为),这是正确的C.