views:

129

answers:

5

Suppose I have a function

void foo(char *)

which, internally, needs to treat its input as a block of NUL-terminated bytes (say, it's a hash function on strings). I could cast the argument to unsigned char* in the function. I could also change the declaration to

void foo(unsigned char *)

Now, given that char, signed char and unsigned char are three different types, would this constitute an interface change, under any reasonable definition of the term "interface" in C?

(This question is intended to settle a discussion raised by another question. I have my opinions, but will not accept an answer until one comes up as a "winner" by the votes of others.)

+3  A: 

Yes it is. Client code which previously compiled will no longer compile (or anyway is likely to generate new warnings), so this is a breaking change.

Steve Jessop
+1: Of course, this depends on a definition of "interface", but if it requires a re-compile, then you've leaked the abstraction to the users of your API.
Oli Charlesworth
@Oli, why does it require a re-compile? In C it doesn't. `char*` and `unsigned char*` are aligned the same. (Well sure *I* would recompile my code, but there is nothing that forces it.)
Jens Gustedt
@Jens: You are correct. I guess what I meant was "if it requires the client to modify their code before it will re-compile".
Oli Charlesworth
+1  A: 

No it isn't. Any change to client code is trivial (especially if it's just to avoid a warning), and in practice in pretty much any C implementation you'll find that you don't even need a re-compile because pointers to char* and unsigned char* will be passed exactly the same way in the calling convention.

Steve Jessop
You're making certain assumptions here which won't always hold. If you're accepting that there is a change required to client code, even if you think it is trivial (and in this case I think that it might not be trivial) you're accepting that there *is* an interface change.
Tim
@Tim: exactly, although I was hoping that people would vote +1 for one of my two answers, rather than -1 for one of them. -1 for both would also be acceptable.
Steve Jessop
I go for this one. Sentence 7 in 6.3.2.3 explicitly allows conversion between pointers to different object types, as long there is no alignment problem, that can't happen here. And it goes on by a statement about "a character type". So for the case here, any call that has been valid remains so. The compiler might issue some nice warnings afterwards, but that is all.
Jens Gustedt
Oh yeah. Sorry. Didn't see they were both *your* answers. :-)
Tim
See also: http://stackoverflow.com/questions/914242/why-is-chars-sign-ness-not-defined-in-c
Tim
@Tim: in light of what you said, though, I've removed "required". I'm not convinced a change is required *by the standard*, even if it's required by the desire to compile warning-free. Arguably it's not my portable code's fault if a particular compiler takes against it. If I'm the author of `foo`, I want to avoid triggering warnings in compilers I know about, so that's a good reason to avoid the change. I'm not sure, though, whether it's generally regarded as an incompatible interface change. So far 3:1 say it is :-)
Steve Jessop
OK, I feel more sympathetic to your new phrasing, but my vote's still with the other camp. Having said that, I'm currently failing to find any *real life* code written reasonably defensively which will cause more than a compiler error if the function prototype was changed. Will comment here if I make any progress. :-)
Tim
A: 

Does a char* implicitly convert to an unsigned char*?

  • Yes - you haven't broken your interface
  • No - you've leaked implementation
    details.
DeadMG
To answer from this POV, you also need `void (*)(unsigned char*)` to convert implicitly to `void (*)(char*)` (and work when called), since someone might have written `void (*pfoo)(char*) = `. I'm a bit confused what C allows, as it happens...
Steve Jessop
@Steve Jessop: That may be a problem. However, it would be pretty trivial to write a function that simply passed the argument on.
DeadMG
@DeadMG: true. For background, though, the original question threw up answers "change the signature to `unsigned char*`, "cast the `char*` to `unsigned char*` in foo", and `cast the `char` to `unsigned` once you've read it". If you end up providing a wrapper function to make up for the change to the signature of `foo`, I think that means casting in `foo` would have been better in the first place. The original argument was, I think, about quite a small thing - is this new foo a compatible replacement for the old one?
Steve Jessop
+1  A: 

I choose "C -- none of the above."

Although it's not a direct answer to the question you actually asked, the right solution to the situation seems fairly simple and obvious to me: you shouldn't really be using any of the above.

At least IMO, you have a really good reason to do otherwise, your function should accept a void * or (preferably) void const *. What you're looking for is basically an opaque pointer, and that's exactly what void * provides. The user doesn't need to know anything about the internals of your implementation, and since any other pointer type will convert to void * implicitly, it's one of the few possibilities that doesn't break any existing code either.

Jerry Coffin
Isn't this an answer to the other question that provoked this one? This question is, "would you call this an interface change?", not "what should I do?"
Steve Jessop
Why would you change it to a `void *`? Thats less type safe and will hurt the user when he gets _no warning_.
mathepic
@mathepic: Type safety makes sense if and only if you can define the type(s) for which the function makes sense. He says he's treating the content simply as bytes, which indicates it's independent of type.
Jerry Coffin
@Steve: Maybe -- I didn't see/look at that one. I didn't bother answering *whether* it is, because that struck me as blindingly obvious: of course it is. You can argue that it's too *small* of one to care much about, but it clearly *is* one regardless of that.
Jerry Coffin
"say, it's a hash function on strings" - It is not generic.
mathepic
@mathepic: yes, he does contradict himself, so the question and the example really lead to different answers.
Jerry Coffin
+3  A: 

According to ISO/IEC 9899:TC3,

  • calling a function through an expression of incompatible type is undefined behaviour (6.5.2.2 §9)
  • compatible function types must have compatible parameter types (6.7.5.3 §15)
  • compatible pointer types must point to compatible types (6.7.5.1 §2)
  • char, signed char and unsigned char are different basic types (6.2.5 §14) and thus incompatible (6.2.7 §1), which is also explicitly mentioned in footnote 35 on page 35

So yes, this is clearly a change to the programming interface.

However, as char *, signed char * and unsigned char * will have identical representations and alignment requirements in any sane implementation of the C language, the binary interface will remain unchanged.

Christoph
I know `char`, `signed char`, and `unsigned char` are different/incompatible basic types. But are `char *`, `signed char *`, and `unsigned char *` incompatible pointer types? I know the section on representation of types makes it well-defined to access signed and unsigned versions of the same type through either type of pointer as long as the value fits in the positive range of the signed type, among other guarantees (such as that `unsigned char *` can be used to access anything).
R..
@R..: right you are, missed that step (fixed now...)
Christoph
+1 and accepted. Thanks for the pointers (signed or unsigned ;) to the standard.
larsmans