【问题标题】:How to cast the address of a pointer generically while conforming to the C standard如何在符合 C 标准的同时一般地转换指针的地址
【发布时间】:2014-07-23 00:24:42
【问题描述】:

通常使用隐式函数返回 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;

【问题讨论】:

  • 我的结论: 1. 虽然我的示例很可能“工作”,但它不符合任何 C 标准,特别是因为它依赖于强制转换为 void **。 2. 我的假设是没有符合标准的方法可以满足上述要求。
  • 更新:在符合标准的同时实现我的要求的唯一方法是实现 (int **) 到 (void **) 转换器。但是由于所述转换是实现定义的,因此只有在兼容的实现下才会符合标准。因此,我的要求是不可能的,我为鹅追逐道歉,非常感谢你的努力。

标签: c pointers strict-aliasing


【解决方案1】:

int**void** 类型不兼容 您将 p(其实际类型为 int**)转换为 void**,然后在此处取消引用:

*((void **) p) = pv;

这将破坏别名规则。

您可以传递一个 void 指针,然后正确地转换它:

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

或者返回一个空指针。

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" ) ;

【讨论】:

  • 我想感谢您的回答以及对严格混叠问题的提及。我的结论是没有符合我要求的方法。
  • @DrorK。嗯...,实际上你可以使用工会,但我不是这方面的专家。我建议您使用我的代码并问一个新问题是否符合标准。我认为是,但我不完全确定。
  • 我想知道如果联合是:union u { void **pp; 你是否仍然认为它有可能符合要求无效 *p; }; ?
  • union 示例与原始示例一样不可移植。写入union 字段“激活”它并停用其他字段:“当值存储在联合类型对象的成员中时,对象表示的字节不对应于该成员但对应于其他成员采用未指定的值”(§6.2.6.1.7)。
  • 你是对的,陷阱表示是唯一剩下的问题。 (malloc 对对齐的保证使得当指针大小相同时这种情况极不可能发生,因此实现真的会欺骗您。)您的解决方案是符合的,只是不严格符合。 +1。
【解决方案2】:

不,这不合规。您将int** 传递为void*(好的),但随后您将void* 转换为void**,这不能保证具有相同的大小和布局。您只能在将 void* 转换回原来的指针类型后取消引用 void*(从 malloc/calloc 获得的除外),并且此规则不适用于递归(因此 void** 不会自动转换,例如 void*)。

我也没有找到满足您所有要求的方法。如果您必须逐个指针传递一个指针,那么您实际上需要传递void* 的地址并在调用者中进行所有必要的转换,在本例中为main。那是

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

...打败你的计划。

【讨论】:

  • 您的回答似乎信息量最大,并明确提到您不知道如何满足我的要求。谢谢
  • 通过'你只能使用一个void*, do you mean you can only dereference a void*`?使用 void 指针(例如,将其分配给另一个指针)而不强制转换它是完全有效的。
  • @WilliamPursell:s/use/dereference,是的。
【解决方案3】:

我认为不可能以 100% 符合标准的方式做到这一点,因为不保证非空指针的大小与 void* 完全相同。

这与标准要求将 printf("%p") 参数显式转换为 void* 的原因相同。

补充:另一方面,一些实现要求这项工作,例如 Windows(它很乐意将 IUnknown** 转换为 void**)。

【讨论】:

  • 所有指向数据成员的指针大小相同,指向函数的指针不需要大小相同。
  • @GradyPlayer 参考? 6.2.5(28):指向其他类型的指针不必具有相同的表示或对齐要求。
  • @GradyPlayer 表示直接指大小。
  • @GradyPlayer 你是说根据你的个人经验,还是你碰巧知道一个符合“void ** 的大小与 int * 的大小相同”的特定标准?跨度>
  • 我可能完全错了,这似乎是共识......所以我将就此辞职......
【解决方案4】:

我认为由于将 void* 转换为 void** 并取消引用它,您的代码可能会带来一些有趣的问题。根据 GCC,这不是问题,但有时 GCC 会撒谎。你可以试试

#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**,这可能会真正混淆您的编译器。由于 main 的 int *pi 被访问并分配了一个 void 指针,因此可能仍然存在 aliasing 问题。但是,在这方面快速浏览 C11 标准并没有定论(请参阅http://open-std.org/JTC1/SC22/WG14/)。

【讨论】:

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

与可以访问任意字节的指针(例如 void*char* 类型的指针)相比,某些平台能够更紧凑地存储只能识别粗对齐对象的指针(例如 int* 类型的对象)。该标准允许以此类平台为目标的实现为int* 保留比void* 更少的空间。在这样做的实现中,允许void** 能够交替更新int*char* 通常是不切实际的;因此,该标准不要求实现支持这种用法。

另一方面,绝大多数实现的目标平台是int*char* 具有相同的大小和表示形式,并且将void* 视为能够操作这两种类型基本上没有成本可互换。根据已发布的基本原理文档,C 精神表明实现不应“阻止程序员做需要做的事情”。因此,如果一个实现声称适用于像低级编程这样可能涉及可互换地处理指向不同类型对象的指针的目的,则无论标准是否要求它都应该支持这种构造;那些在平台上不支持这种结构的平台,它们基本上不需要任何成本,应该被认为不适合任何可以从中受益的目的。

像 gcc 和 clang 这样的编译器需要使用 -fno-strict-aliasing 来使它们支持这样的结构;在许多情况下,在适当的情况下,获得良好的性能可能需要使用restrict。另一方面,由于利用-nno-strict-aliasing 提供的语义并正确使用restrict 的代码可能会比使用严格符合的代码获得更好的性能,因此对此类代码的支持应被视为“流行扩展”之一" 在已发布的基本原理的第 11 页的第 27 行中提到。

【讨论】:

    猜你喜欢
    • 2013-05-17
    • 2016-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-04
    相关资源
    最近更新 更多