char的有无符号是一个接口问题吗?

4
假设我有一个函数:
void foo(char *)

这段代码中,需要将输入的内容内部视为一块以NUL结尾的字节(比如说对字符串进行哈希处理)。在函数中,我可以将参数强制转换为unsigned char*类型。也可以更改声明方式为:

void foo(unsigned char *)

现在,鉴于charsigned charunsigned char三种不同的类型,在C语言中,根据任何合理的“接口”定义,这是否构成了一个接口更改?
(这个问题旨在解决由另一个问题引起的讨论。我有自己的观点,但在其他人的投票中,只有一个答案被选为“获胜者”,我才会接受它。)

2
你为什么在提问只涉及C语言的情况下打上了C++的标签? - Jens Gustedt
@sbi:如果您实际查看他的链接,就可以引用C ++标准并提供C ++示例代码。 OP似乎只是假设这与C标准相同。 - Puppy
将链接更改为指向C标准解释的引用。 - Fred Foo
1
我在这里没有看到任何真正的答案。一个真正的答案应该引用处理函数指针类型的标准部分。 - R.. GitHub STOP HELPING ICE
5个回答

4
根据 ISO/IEC 9899:TC3 标准,
- 通过不兼容类型的表达式调用函数是未定义行为(6.5.2.2 §9) - 兼容的函数类型必须具有兼容的参数类型(6.7.5.3 §15) - 兼容的指针类型必须指向兼容的类型(6.7.5.1 §2) - char、signed char 和 unsigned char 是不同的基本类型(6.2.5 §14),因此不兼容(6.2.7 §1)。这也在第35页的脚注35中明确提到。
所以,是的,这显然是对编程接口的更改。
然而,由于 char*、signed char* 和 unsigned char* 在 C 语言的任何合理实现中具有相同的表示和对齐要求,因此二进制接口将保持不变。

我知道 charsigned charunsigned char 是不同/不兼容的基本类型。但是 char *signed char *unsigned char * 是否是不兼容的指针类型呢?我知道类型表示部分使得通过任一类型的指针访问相同类型的有符号和无符号版本成为明确定义,只要该值适合有符号类型的正范围内,以及其他保证(例如 unsigned char * 可用于访问任何内容)。 - R.. GitHub STOP HELPING ICE
谢谢指导(无论是有符号还是无符号的),+1并已接受。 - Fred Foo

3

是的,它是一项破坏性变更。先前编译的客户端代码将不再编译(或者可能会生成新的警告),因此这是一项破坏性变更。


1
当然,这取决于“接口”的定义,但如果需要重新编译,则已将抽象泄漏给API的用户。 - Oliver Charlesworth
1
@Oli,为什么需要重新编译?在C语言中不需要。char*unsigned char*对齐方式相同。(当然,我会重新编译我的代码,但没有强制要求。) - Jens Gustedt
@Jens:你说得对。我想我的意思是“如果需要客户端在重新编译之前修改他们的代码”。 - Oliver Charlesworth

2

我选择“C--以上都不是”。

虽然这并不是你实际问的问题的直接答案,但对于这种情况的正确解决方案对我来说似乎非常简单明了:你实际上不应该使用任何一个选项。

至少在我看来,如果你有一个很好的理由,你的函数应该接受void *或(最好)void const *。你要找的基本上是一个不透明指针,而void *正是提供这个的。用户不需要知道您的实现内部的任何信息,而且由于任何其他指针类型都会隐式转换为void *,因此它是少数几个不会破坏任何现有代码的可能性之一。


这不是对引发这个问题的另一个问题的回答吗?这个问题是,“你会称之为接口更改吗?”,而不是“我该怎么做?” - Steve Jessop
为什么要将它更改为 void *?这样做会减少类型安全性,并且当用户没有收到警告时会对其造成伤害。 - alternative
@mathepic:只有在您可以定义函数有意义的类型时,类型安全才有意义。他说他只是将内容视为字节,这表明它与类型无关。 - Jerry Coffin
@Steve:也许吧——我没有看到/关注那个。我没有费心回答它是否是,因为那让我感到非常明显:当然是。你可以争论它太小而不值得过多关注,但无论如何,它显然是一个。 - Jerry Coffin
1
说,这是一个针对字符串的哈希函数。它不是通用的。 - alternative
@mathepic:是的,他自相矛盾,所以问题和例子实际上得出了不同的答案。 - Jerry Coffin

0
不是这样的。对客户端代码进行任何更改都是微不足道的(特别是如果只是为了避免警告),而且在实践中,在几乎任何C实现中,您会发现您甚至不需要重新编译,因为指向char*unsigned char*的指针将以相同的方式在调用约定中传递。

你在这里做出了某些假设,这些假设并不总是成立的。如果你接受客户端代码需要进行更改,即使你认为这很琐碎(在这种情况下,我认为它可能不是琐碎的),你就接受了存在接口更改。 - Tim
我选择这个。6.3.2.3节中的第7句明确允许在没有对齐问题的情况下转换不同对象类型的指针,这里不可能发生这种情况。接着它提到了“字符类型”的语句。因此,在这种情况下,任何有效的调用仍然有效。编译器可能会发出一些好的警告,但那就是全部了。 - Jens Gustedt
哦,对不起,没有看到它们都是的回答。 :-) - Tim
参见:https://dev59.com/2HNA5IYBdhLWcg3wjOre - Tim
好的,我对你的新措辞感到更加同情,但我的投票仍然支持另一方。话虽如此,我目前还没有找到任何合理防御性编写的现实生活代码,如果函数原型被更改,它将导致更多的编译器错误。如果我有任何进展,我会在这里评论。 :-) - Tim
显示剩余2条评论

0

一个char*会隐式转换为unsigned char*吗?

  • 是 - 你没有破坏接口
  • 否 - 你泄漏了实现细节。

1
为了从这个角度回答,您还需要void (*)(unsigned char*)隐式转换为void (*)(char*)(并在调用时正常工作),因为有人可能会写void (*pfoo)(char*) = &foo;。就我而言,我有点困惑C语言允许什么,就是这样... - Steve Jessop
@Steve Jessop:那可能是一个问题。然而,编写一个简单地将参数传递的函数相当容易。 - Puppy
对于背景知识,原始问题提出了以下答案:“将签名更改为unsigned char*”,“在foo中将char*强制转换为unsigned char*”,以及“一旦读取完毕,将char强制转换为unsigned”。如果最终需要提供一个包装函数来弥补对foo签名的更改,我认为在第一次就在foo中进行强制转换可能更好。我认为原始论点是关于一个相当小的事情 - 这个新的foo是否能兼容替代旧的foo? - Steve Jessop

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