在C语言中将常量转换为非常量

9

我正在进行一个学校项目,需要重现许多C库函数。但是我在其中遇到了一些困难。

如果你查看memchr的man页面,你会看到它将const void *作为输入并返回一个普通的void *。我会假设在函数的某个地方,他们将返回变量从const转换为非const

然而,当我这样做时(clang -Weverything +Werror),它无法编译。没有-Weverything标签时可以工作,但是如果可能的话,我更喜欢使用它。

有任何“正确”的方法吗?


3
如果你发现需要将一个常量转换为非常量,那么在大多数情况下,你可能正在做一些错误的事情。然而,在例如memchr这样的情况下,这可能是必要的。 - Some programmer dude
4
您能否展示一个 [MCVE] 以演示这个情况?(注:[MCVE] 表示“最小完整可复现示例”,即在尽可能简短的代码中重现问题的示例) - t0mm13b
3
针对你的问题,我认为如果你将问题修改成关于警告和错误信息会更好一些。这需要你编辑问题,包括提供一个 最小化、完整化、可重现例子,以及实际出现的错误信息。如果你还没有这样做,请先阅读 如何提出好问题的指南。请注意不要改变原来的意思。 - Some programmer dude
3
C++ 提供了两个重载函数:const void *memchr(const void *, int, size_t)void *memchr(void *, int, size_t),这是为了避免该函数破坏 const。然而,C 不允许函数重载。 - ephemient
2
最后,-Weverything 是过度的。 -Wall -Wextra -pedantic 通常足够开始使用。 使用 -Weverything 你会得到很多错误的警告,这些警告可能与当前问题无关或不必要,或者完全是错误的。 - Some programmer dude
显示剩余5条评论
3个回答

4
问题是在特定位置触发了不需要的GCC风格(包括Clang)诊断,这是由于-Weverything 包含了-Wcast-qual选项。解决方案是只在那个地方禁用仅仅-Wcast-qual
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
    void* non_const_ptr = (void*)const_ptr;
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

将非指针类型强制转换为指针类型,然后再转换回来将阻止所有有用的诊断,从而使-Weverything失去意义。

#ifdef __GNUC__是为了更好的可移植性而包含的。一些编译器会警告他们不认识的#pragma,除非这样的#pragma被#ifdef掉。


3
正如您所指出的,一些C库函数必须将其const指针参数强制转换以删除返回值的const限定符:memchr,strchr,strstr等。其他标准函数将解析字符串的末尾指针存储到char **中,尽管它指向将const char *传递给它们的数组:strtol,strod等。
如果您的编译器对此进行警告并产生警告,请尝试在转换为unsigned char *之前将其强制转换为uintptr_t。您还可以使用具有两种指针类型的union。
C标准是这样指定memcpy的:

7.24.5.1 The memchr function

Synopsis

#include <string.h>

void *memchr(const void *s, int c, size_t n);

Description

The memchr function locates the first occurrence of c (converted to an unsigned char) in the initial n characters (each interpreted as unsigned char) of the object pointed to by s. The implementation shall behave as if it reads the characters sequentially and stops as soon as a matching character is found.

Returns

The memchr function returns a pointer to the located character, or a null pointer if the character does not occur in the object.

如果您无法使用其他类型,则可以使用size_t进行转换以抑制编译器警告,以下是可能的实现方式:
void *my_memchr(const void *ptr, int c, size_t num) {
    const unsigned char *cptr = ptr;

    while (num-- > 0) {
        if (*cptr++ == (unsigned char)c) {
            /* const pointer is cast first as size_t to avoid a compiler warning.
             * a more appropriate type for this intermediary cast would be uintptr_t,
             * but this type is not allowed here.
             */
            return (void *)(size_t)(cptr - 1);
        }
    }
    return NULL;
}

更新:

使用return cptr - 1;将会发出警告,因为隐式转换为void *时,const限定符被移除。

显式地将返回值强制转换为return (void *)(cptr - 1);就足够了,但是一些编译器仍然会发出警告,以帮助程序员避免错误。使用额外的中间转换return (void *)(uintptr_t)(cptr - 1);return (void *)(size_t)(cptr - 1);并不会改变转换的语义,因为uintptr_tsize_t应该足够大,可以在指针和整数类型之间进行往返转换,并且通常可以停止编译器发出的警告。


很遗憾,我只能使用size_t和内置类型,不能使用其他数据类型。如果使用Pragma禁用特定检查,这是否是不良实践? - ryan27968
@user1803425:我想你可以将指针强制转换为(size_t),它在大多数当前架构上与指针大小相同,但这并不是严格正确的做法,而定义一个union会更简单,使用标准类型uintptr_t则是正确的。 - chqrlie
@user1803425 在这里,您应该使用一个#pragma指令。它可以记录意图,使代码完全跨平台兼容,并允许您在违反特定const-cast检查的那一行之后立即恢复(使用“pop”)。 - Qix - MONICA WAS MISTREATED
1
@Qix:编译指示本质上是不可移植的。适用于clang的适当编译指示可能会对不同编译器产生意外行为,因此应该用适当的#ifdef测试来包围它,这使得整个过程相当丑陋。 - chqrlie
@chqrlie 对于这样的例外情况,它们还是很好的。如果你想要漂亮的代码,C预处理器并不能帮助你太多。 - Qix - MONICA WAS MISTREATED
显示剩余2条评论

1
这个技巧可以实现。实际上,在除了最不常见的平台之外,sizeof(void *)等于sizeof(size_t)。然而,我建议不要使用它。你应该放弃-Weverything。标准C函数可以追溯到70年代,最初的C编译器比今天启用所有警告的Clang或GCC要宽松得多。你会发现一些函数中存在“不安全”的事实是不可避免的。
void * memchr_(const void * ptr_, int c, size_t num);

int main(void)
{
    unsigned char ary[] = { 1, 6, 2, 45, 23, 75, 23, 43, 23 },
                  * ptr = NULL;

    ptr = memchr_(ary, 23, sizeof(ary) / sizeof(ary[0]));
    printf("ary = %p, ptr = %p, *ptr = %u\n", (void *)ary, (void *)ptr, *ptr);
    return 0;
}

void * memchr_(const void * ptr_, int c, size_t num)
{
    size_t i;
    const unsigned char * ptr = ptr_;

    for(i = 0; i < num; i++) {
        if(ptr[i] == (unsigned char)c) {
            /* Casting to size_t first so that the compiler doesn't complain */
            return (unsigned char *)(size_t)ptr + i;
        }
    }
    return NULL;
}

感谢 size_t 强制转换就能解决问题。我使用 -Weverything 的原因是因为我的强迫症,而且它还可以捕获一些有用但不包含在 -pedantic 中的特定选项,例如 -Wconversion。 - ryan27968
你的解决方案在带有有符号字符的架构上,对于超过CHAR_MAX字节值存在问题。memchr的正确语义要求您编写if (ptr[i] == (unsigned char)c) - chqrlie
2
@user1803425:我建议您在上面的代码中添加一条额外的注释,说明应该使用uintptr_t而不是size_t - chqrlie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接