Does C provide an operator to check the signedness of a type?
Asked Answered
C

2

6

If I'm using a library which currently uses a particular numeric type alias, e.g.

typedef uint32_t library_type;

void library_function(library_type x);

How would I ensure that code which needs to pass in a value from a different type remains correct, even if the library changes its typedef?

uint64_t x = get_some_number();

// …need checks here…

library_function((library_type)x);

I could add the following checks:

assert(sizeof library_type >= sizeof uint32_t);
assert(x <= UINT32_MAX);

The second check would ensure the value I get fits within the range of the current library_type. The library author does not provide a LIBRARY_TYPE_MAX definition, so the first check tries to guard the second, in case library_type were to change for whatever reason when the code is compiled in the future.

That first check would catch if library_type were change to e.g. int8_t, but what if the library_type were changed to an int32_t instead? It's still the right "size" but the range is still less than what I am checking!

Does the C language provide an operator that introspects the signedness of a type, in the same way that sizeof lets me know the width? Is there any other way to make sure my cast to library_type will only be reached if it is correct?

Coffee answered 12/12, 2019 at 19:25 Comment(10)
You are confusing runtime and compile time checks. C doesn't really do dynamic types at runtime (unless you build in some scheme of your own for storing the type which should be used to re-interpret what C would originally view as raw data), so any check you do to determine if the source code has been changed would have to be at compile time, not a runtime check like assert().Franckot
Looks like C++11 provides std::is_signed; here I'm wondering if there's anything similar in C (and also whether this is the right approach at all ;-)Coffee
The right approach is to make sure this can't happen. Most libraries define their own types. If someone edits the source code, they rather than you own responsibility. Though it is possible for a build script to build in some sense of integrity against non-malicious actors, for example capture its own git state in a version field.Franckot
@ChrisStratton For the purposes of the question, I don't care if it's a runtime check or a compile time check. My assert(x <= …) check would need to be runtime; making sure that check is correct could be compile time or runtime.Coffee
Are you trying to protect against mistaken edits of the library's source code, or mistaken parameters passed to the library at runtime? Neither problem is particularly solvable, but the question is really unanswerable until you clarify your exact fear.Franckot
@ChrisStratton Think of my question as "how can I ensure a [numeric] cast remains correct when I have no current/future control over the type I am casting to?"Coffee
Can you not remove the explicit cast and then catch incorrect implicit conversions with -Wconversion? Get something like: conversion to uint32_t {aka unsigned int} from int32_t {aka int} may change the sign of the resultChaunceychaunt
@Chaunceychaunt Hmm, there might be a way but the cast is already necessary afaict since I'm going from a uint64_t (which "probably" doesn't store a big number in practice) to something smaller.Coffee
Well then cast it to the type you want checked before calling the function. uint32_t x_cast = (uint32_t) x; library_function(x_cast);Chaunceychaunt
@Chaunceychaunt Yes! I think that's the solution, and nicer than my own idea which was to only cast if within INTn_MAX instead of UINTn_MAX.Coffee
B
7

Yes, the relational operators combined with cast operator. For example:

_Bool TYPE_is_signed = ((TYPE)-1 < 0);

Or as an assertion:

assert((TYPE)-1 < 0); // Require that TYPE is signed
assert((TYPE)-1 > 0); // Require that TYPE is unsigned
Brilliant answered 12/12, 2019 at 20:1 Comment(2)
Neat, and could be turned into a macro if needed. In practice I might prefer @kaylum's solution of using an intermediate cast to the current underlying type (e.g. cast to uint32_t instead of library_type) and let compiler catch any breaking changes. But this answers my question as posed.Coffee
assert(((TYPE1)-1 < 0) == ((TYPE2)-1 < 0));Brilliant
F
2

How would I ensure that code which needs to pass in a value from a different type remains correct, even if the library changes its typedef?

The situation you are trying to defend against is one of irresponsible human behavior rather than a technical one.

Changing a typedef of an argument in an API is a "breaking change" - just as any other change in behavior should be. Such a change is, if more than a bugfix, also likely a hint at others which may be more subtle and severe.

Breaking changes should be documented in release notes.

Typically, if there is automated enforcement of a breaking change in an API, it is by means such as version identifiers or capability queries which can be checked at build and/or runtime - somewhere in the library headers would be #define LIBRARY_WHATEVER_VERSION 3 and then your code can have preprocessor directives to check that.

When a breaking change is really severe, often the software itself is renamed such that the wrong version would not even satisfy #include or link attempts.

Franckot answered 12/12, 2019 at 19:39 Comment(2)
I think you're getting distracted by the design of this hypothetical library, which happens to be controlled by a huge corporation that cares nothing for our opinions. Can I write portable C code which detects if a number can be cast to a smaller but otherwise ± unknown type?Coffee
I think you're getting distracted by one breaking change and ignoring others. And your provider isn't going to just change things without bumping a version number. Take time to understand the changes before you start using a new version! Have your build system check an md5sum of the headers if there is no encoded versioning and you think a co-worker might be sloppy in setting up a build. Better yet, capture all the vendor deliverables into a git submodule, even if they just dump header + binary zipfiles on you, you can still track their contents and see meaningful header diffs in git.Franckot

© 2022 - 2024 — McMap. All rights reserved.