如何在符合C标准的情况下一般地转换指针的地址
通常使用隐式函数返回void *转换来指定带有分配的指针,就像malloc()一样:
void *malloc(size_t size); int *pi = malloc(sizeof *pi);
我希望在传递目标指针的地址时执行相同的赋值,而不从函数内部(不在其体内,也不在参数中)显式地转换它的类型。
以下代码似乎实现了这一点。
- 我想知道代码是否完全符合(任何)C标准。
- 如果它不符合,我想知道是否有可能在符合(任何)C标准的情况下达到我的要求。
。
#include #include int allocate_memory(void *p, size_t s) { void *pv; if ( ( pv = malloc(s) ) == NULL ) { fprintf(stderr, "Error: malloc();"); return -1; } printf("pv: %p;\n", pv); *((void **) p) = pv; return 0; } int main(void) { int *pi = NULL; allocate_memory(&pi, sizeof *pi); printf("pi: %p;\n", (void *) pi); return 0; }
结果:
pv: 0x800103a8; pi: 0x800103a8;
不,这不合规。 你将int**
作为void*
(ok)传递,但是然后你将void*
转换为void**
,这不能保证具有相同的大小和布局。 在将其转换回原来的指针类型之后,你只能取消引用void*
(除了从malloc
/ calloc
获得的一个),并且此规则不会递归应用(因此void**
不会自动转换,如void*
)。
我也没有办法满足你的所有要求。 如果必须通过指针传递指针,则需要实际传递void*
的地址并在调用者中执行所有必要的转换,在本例中为main
。 那就是
int *pi; void *pv; allocate_memory(&pv, sizeof(int)); pi = pv;
……打败你的计划
类型int**
和void**
不兼容您正在将其实际类型为int **的p转换为void **,然后在此处取消引用它:
*((void **) p) = pv;
这将破坏别名规则。
您可以传递一个void指针然后正确地转换它:
void *pi = NULL; int* ipi = NULL ; allocate_memory(&pi, sizeof *ipi ); ipi = pi ;
或返回一个void指针。
int *pi = allocate_memory(sizeof *pi);
可以选择使用联合:
#include #include #include union Pass { void** p ; int** pi ; } ; int allocate_memory(union Pass u , size_t s) { void *pv; if ( ( pv = malloc(s) ) == NULL ) { fprintf(stderr, "Error: malloc();"); return -1; } printf("pv: %p;\n", pv); *(up) = pv; return 0; } int main() { int* pi = NULL ; printf("%p\n" , pi ) ; allocate_memory( ( union Pass ){ .pi = &pi } , sizeof( *pi ) ) ; printf("%p\n" , pi ) ; return 0; }
据我了解,这个例子符合标准。
使用静态断言来保证大小和对齐方式相同。
_Static_assert( sizeof( int** ) == sizeof( void** ) , "warning" ) ; _Static_assert( _Alignof( int** ) == _Alignof( void** ) , "warning" ) ;
我认为不可能以100%符合标准的方式进行,因为非void指针不能保证与void*
具有完全相同的大小。
这与标准要求将printf("%p")
参数显式转换为void*
。
补充:另一方面,一些实现要求这项工作,例如Windows(很高兴地将IUnknown**
转换为void**
)。
我认为您的代码可能会提供一些有趣的问题,因为将void *转换为void **并取消引用它。 根据海湾合作委员会的说法,这不是一个问题,但有时候海湾合 你可以试试
#include #include int allocate_memory(void **p, size_t s) { if ( ( *p = malloc(s) ) == NULL ) { fprintf(stderr, "Error: malloc();"); return -1; } return 0; } int main(void) { int *pi = NULL; if ( allocate_memory((void**) &pi, sizeof *pi) == 0 ) { printf("pi: %p;\n", (void *) pi); } return 0; }
请注意,在原始代码中,您必须将int**
为void*
(隐式),然后显式转换为void**
,这可能会使您的编译器感到困惑。 可能仍然存在别名问题,因为main的int *pi
被访问并分配了一个void
指针。 但是,在这方面快速扫描C11标准尚无定论(见http://open-std.org/JTC1/SC22/WG14/ )。
一些平台能够存储指针,这些指针只能识别粗略对齐的对象(例如int*
类型的对象)比可以访问任意字节的指针(例如void*
或char*
类型的指针)更紧凑。 标准允许针对此类平台的实现为int*
保留的空间少于void*
。 在这样做的实现上,允许void**
能够互换地更新int*
或char*
通常是不切实际的; 因此,标准不要求实现支持这种用法。
另一方面,绝大多数实现都以int*
和char*
具有相同大小和表示的平台为目标,并且在将void*
视为能够互换地操作两种类型时基本上没有任何成本。 根据已发布的Rationale文档,C的精神表明实现不应该“阻止程序员做必须做的事情”。 因此,如果一个实现声称适用于低级编程这样的目的,可能涉及交替处理指向不同类型对象的指针,它应该支持这样的结构,无论标准是否要求它这样做; 那些不支持这种结构的人在基本上没有任何成本的平台上应该被认为不适合任何可以从中受益的目的。
像gcc和clang这样的编译器需要使用-fno-strict-aliasing
来使它们支持这样的结构; 在适当的情况下,获得良好性能可能需要在许多情况下使用restrict
。 另一方面,由于利用通过-nno-strict-aliasing
可用的语义并且正确使用restrict
可以实现比严格符合代码所能获得的更好的性能,并且对这样的代码的支持应被视为“流行”之一扩展“在公布的理由的第11页第27行提到。