问题 如何在符合C标准的情况下一般地转换指针的地址


通常使用隐式函数返回void *转换来指定带有分配的指针,就像malloc()一样:

void *malloc(size_t size);
int *pi = malloc(sizeof *pi);

我希望在传递目标指针的地址时执行相同的赋值,而不从函数内部(不在其体内,也不在参数中)显式地转换它的类型。

以下代码似乎实现了这一点。

  1. 我想知道代码是否完全符合(任何) C标准。
  2. 如果它不符合,我想知道是否可能 满足我的要求,同时符合(任何)C标准。

#include <stdio.h>
#include <stdlib.h>

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;

5926
2018-06-02 19:46


起源

我的结论:1。虽然我的样本可能“有效”,但它不符合任何C标准,特别是因为它取决于铸造到空隙**。 2.我的假设是没有符合上述要求的标准符合方法。 - Dror K.
更新:在符合标准的同时实现我的要求的唯一方法是实现(int **)到(void **)转换器。但由于所述转换是实现定义的事实,它仅在兼容的实现下符合标准。因此我的要求是不可能的,我为鹅追逐道歉,非常感谢你的努力。 - Dror K.


答案:


不,这不合规。你通过了 int** 如 void* (好的),但是你投了 void* 到了 void** 不保证具有相同的大小和布局。你只能取消引用一个 void* (除了一个人来自 malloc/calloc)将它转换回原来的指针类型后,此规则不会递归应用(所以a void** 不会自动转换,就像一个 void*)。

我也没有办法满足你的所有要求。如果必须通过指针传递指针,则需要实际传递a的地址 void* 在这种情况下,在调用者中进行所有必要的转换 main。那就是

int *pi;
void *pv;
allocate_memory(&pv, sizeof(int));
pi = pv;

......打败你的计划


5
2018-06-02 19:55



你的回答似乎是最有用的,并明确提到你不知道如何满足我的要求。谢谢 - Dror K.
'你只能用空*, do you mean 你只能取消引用空格*`?使用void指针(例如,将其指定给另一个指针)而不强制转换它是完全有效的。 - William Pursell
@WilliamPursell: s/use/dereference是的 - Fred Foo


类型 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 <stdio.h>
#include <stdarg.h>
#include <stdlib.h>

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);
    *(u.p) = 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" ) ;

6
2018-06-02 19:55



我想感谢你的回答,并提到严格别名的问题。我的结论是,没有符合要求的方式来实现我的要求。 - Dror K.
@DrorK。嗯......,实际上你可以使用联盟,但我不是那个专家。我建议你使用我的代码并询问一个新的问题,如果它符合标准。我想是的,但我不完全确定。 - this
我想知道你是否仍然认为如果工会是合适的话,它有可能符合:union u {void ** pp; void * p; }; ? - Dror K.
该 union 示例与原始版本一样不可移植。写给一个 union 字段“激活”它并停用其他字段:“当一个值存储在union类型的对象的成员中时,对象表示的字节与该成员不对应但与其他成员对应的字节采用未指定的值”( §6.2.6.1.7)。 - Fred Foo
你是对的,陷阱表示是剩下的唯一问题。 (malloc当指针大小相同时,对于对齐的保证非常不可能,因此实现真的会对你起作用。)你的解决方案是符合的,只是不严格符合。 +1。 - Fred Foo


我认为不可能以100%符合标准的方式进行,因为不能保证非void指针的大小与 void*

这与标准要求明确铸造的原因相同 printf("%p") 参数 void*


2
2018-06-02 19:54



所有指向数据成员的指针都具有相同的大小,指向函数的指针不需要具有相同的大小。 - Grady Player
@GradyPlayer参考? 6.2.5(28): 指向其他类型的指针不需要具有相同的表示或对齐要求。 - this
@GradyPlayer表示直接指大小。 - this
@GradyPlayer你是根据你的个人经验说的,或者你碰巧知道一个符合“虚空的大小**与int *的大小相同”的特定标准吗? - Dror K.
我可能只是完全错了,这似乎是共识......所以我只会辞职...... - Grady Player


我认为您的代码可能会提供一些有趣的问题,因为将void *转换为void **并取消引用它。根据海湾合作委员会的说法,这不是一个问题,但有时候海湾合你可以试试

#include <stdio.h>
#include <stdlib.h>

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** 这可能真的让您的编译器感到困惑。可能还有一个 别名 问题是由于主要的事实 int *pi 被访问并分配了一个 void 指针。但是,快速扫描C11标准在这方面尚无定论(见 http://open-std.org/JTC1/SC22/WG14/)。


0
2018-06-02 20:31



您正在将int **转换为void **,这是非法的。 - this
@self:不是 非法 (即,它不违反约束),但它可能具有未定义的行为。 - Keith Thompson
@KeithThompson这看起来很具体:6.2.5(28): 指向其他类型的指针不需要具有相同的表示或对齐要求。 - this
@self:当然。我的狡辩是“非法”这个词 - C标准实际上没有使用的术语。我倾向于仅将这个词用于违反语法错误和约束,即。必须诊断的事情(尽管编译器在发出警告后可以合法地接受程序)。 - Keith Thompson
@self。:在这种情况下,未定义的行为。实际上并没有 打破 任何规则;该标准特别允许在任何标量类型之间进行转换,如果您碰巧知道它在当前系统上有效,这样的转换甚至可能是有效的。它是不可移植的,而不是非法的 - 而C的最大优势之一是能够编写非可移植代码。 (话虽如此,转换 int** 至 void** 在任何情况下都可能不是一个好主意。) - Keith Thompson