C – Preliminary C2x Charter
open-std.org
open-std.org
I'm a pretty hardcore C programmer (been using it as my absolute main and favorite language for over 20 years), yet I find it hard to come up with some wishes for what I'd like to see.
Not sure how to interpret that, perhaps it's just indicative of a certain ... conservatism common in old people? :) Scary thought!
To be brutally concrete, I sometimes wish I could write someting like this:
do {
const bool flag = do_something();
...
...
} while(!flag);
That'd be very useful, but of course in current standards it's not possible since 'flag' isn't valid outside the scope it's declared in. So now we have to pre-declare 'flag' before the loop, breaking const-cleanliness too of course. This is obviously not a show-stopper, it's a minute thing. But it would be so nice! :)Some support for automatic type inference would be nice, since most right-hand-side expressions have a type anyway it would be cool to be able to write
auto x = 3;
and have the type of 'x' be 'int' since that is the type of the literal '3'. Of course re-using "auto" like this would break their guiding principle, but it would look great (and, I think, somewhat mimic what C++ has done to that keyword).Does anyone have some better ideas?
gcc's C extensions have some good ideas [1], particularly:
1. typeof(e) operator: great for use in macros
2. labels as values: used in just about every language interpeter
3. statement expressions: great for use in macros
4. fixed point types: who wants to hack their own fixed precision arithmetic?
5. vector extensions
6. arithmetic functions with overflow checking
I think all of the above would make good additions to the C standard.
I'd like to see the gcc ".." operator for range-based switch cases and designated initializers become standardized.
Do hardware constraints make, for example, an 8.8 bit format easier to handle than a 12.4 format?
Compare Ada's fixed-point types, which let you specify the range and delta:
type Fixed is delta 0.0625 range 0.0 .. 4095.9375;Sometimes they do. For example, the ARMv7 DSP extensions support signed Q0.31 and signed Q0.15 arithmetic much faster than any other fixed-point format.
I'd rather see the ability to place an attribute on a floating-point variable that would inform the compiler that fixed-point might be suitable. You'd specify the requirements that need to be met, and the compiler would choose the fastest format for the current architecture. That format could be fixed-point, decimal float, IEEE float, or something weirder.
For example, your requirements could include: minimum magnitude, maximum magnitude, minimum value, maximum value, maximum additive error, maximum multiplicative error, signed zero needed or not, NaN signed/unsupported/unsigned, infinity distinct from NaN and/or the largest number, etc.
You say what you need for correctness, and the compiler makes it happen.
It's not maintainable.
We should be able to take that prototype, mark it up with hints for the compiler, and then build with fixed-point enabled.
while (true) {
const bool flag = do_something();
if (!flag) break;
}Principle 13: Unlike for C99, the consensus at the London meeting was that there should be no invention, without exception. Only those features that have a history and are in common use by a commercial implementation should be considered
#define AUTO(var, def) __typeof__(def) var = (def)
AUTO(x, 3);static lifetime compound literals. I don't see any reason not to be able to get the same effect as string literals for other types like:
return (static int const []){1, 2, 3};
Disallowing branch-predicted values for relaxed atomic operations. I think c++ did this and it seems the sane way to go.
Binary literals and binary printf format specifier. It would just occasionally be nice compared to hex.
countof macro for arrays. I don't even care if it checked for non-array type errors, it would just be nice for it to be standard.
Statement expressions and function literals would be nice, but I don't feel strongly about it.
_Auto
and then add a header like <stdauto.h> which does #define auto _Auto
like they did with _Bool in C99?1.8: What's the auto keyword good for?
Declaring vehicles.
struct car { ... };
which would get cumbersome to type.But "auto int x = 3;" is valid (and the "auto" keyword is useless in that context).
do {
} while(!do_something());
Not trying to be snarky, I'd just like to understand :) do {
const bool flag = do_something();
do_something_else();
} while(!flag);
Or like this: do {
int whatever = get_some_data();
const bool flag = do_something(whatever);
} while(!flag); while (true) {
const bool flag = do_something();
if (!flag) break;
}
Is it not what grandparent wanted?Not language, rather standard library, but I would like to see a new allocation API with explicit control over alignment, separation of reserving and committing addresses, and generally updated for the fact that memory systems are different now than they were in 1979.
aligned_alloc is alright, except there's no aligned_realloc. Linux always reserves addresses and doesn't commit until it needs to, but that can't be relied on for portable code.
In short: we're in a 64-bit, virtual memory world. I don't want an allocator design from 1979.
It's about time glibc's custom streams[1] get standardized. Real talk, it's 2016 and we can't define custom FILE * types. This doesn't even need to expose the libc's FILE implementation; it can remain opaque just fine.
Language-wise, I'd like to see a pragma to covert an array of structs to a struct of arrays; i.e. declare
#pragma struct SOA
struct entity {
uint32_t id;
...
};
struct entity all_entities[1000];
and have the compiler rewrite it to struct {
uint32_t id[1000];
...[1000];
} all_entities; // where typeof(all_entites[N]) == typeof(struct entity)
à la Jai[2]. This isn't a particularly hard transformation to do by hand, but it's kind of tedious.Standardizing M4 as a preprocessor would be a nice-to-have; i.e. you should be able to compile a .h.m4 or .c.m4 file directly. I don't know why GCC can't do this yet. It's a bit of a niche thing, but some of us are M4 addicts.
C is in a pretty good place otherwise. I like to think there are 3 known local optima in terms of programming languages: C, Lisp, and Forth. All of them are a little hard at first, but quickly become very easy to think in.
We'll probably never get any of this, which is a little depressing. I get the feeling the C committee doesn't really care that much about evolving the language any more. Modern, fast, safe(-ish), readable code is a GCC extension.
1. http://www.gnu.org/software/libc/manual/html_node/Custom-Str... 2. https://github.com/BSVino/JaiPrimer/blob/master/JaiPrimer.md...
The big problem with extending the C language is that C's in a ton of places. Some of the things you describe would mean increasing the size of the C runtime, which would be unacceptable in embedded programming and the like. Perhaps we need a middle ground language here -- more daring than C, more conservative than C++ -- where ideas can be tuned better and/or be specified for system types. C's universal assembly role really is a double edged sword as far as getting new stuff in the language.
Adding Lisp-style AST macros to C is never gonna happen[1], though it would be cool. I'm of the opinion that text macros and ‘real’ macros complement each other anyway.
One of these days I'll get around to implementing a less-portable-more-cool-C. There's Jai[2], but I don't really agree with all of its design (understandable, since I'm not a game developer).
--
1. Just kidding, people have done it already; e.g. https://github.com/eudoxia0/cmacro but it'll probably never be part of the standard.
2. https://github.com/BSVino/JaiPrimer/blob/master/JaiPrimer.md
int x = something();
do {
const int x = somethingElse();
...
} while (x != 42);For instance, if you overflow an integer, the compiler should not be allowed to assume that code branch is unreachable, or to conjure nasal demons. Instead the result should be an implementation-defined integer or a jump to an implementation-defined exception handling routine.
C should be predictable!
As someone who's worked on compilers a good bit, I disagree (and this is my favorite meme to argue against lately). You need that undefined behavior for performance.
> For instance, if you overflow an integer, the compiler should not be allowed to assume that code branch is unreachable, or to conjure nasal demons.
You need to be careful to not destroy loop trip count detection.
> Instead the result should be an implementation-defined integer
Not good enough. Loop trip count detection destroyed.
> jump to an implementation-defined exception handling routine.
Unacceptable for performance. Probably a 3x loss.
Yes. Other languages like Swift generally don't need it either.
The big wins from exploiting undefined behavior in C come from mistakes that C's designers made in 1978: signed int being the easiest thing to use for loops over array indices, the any-pointer-can-alias-anything-else style, the uselessness of const for optimization, and null pointers. Rust (and e.g. Swift, for the most part) made none of these choices, and so they can reap the benefits of aggressive compiler optimizations without the undefined behavior.
(Digression: I can't blame C's designers for these mistakes, of course: it was 1978! But I think that in 2016 we should just accept that there were things we didn't do right in 1978, because we didn't know as much then as we do now. In most other engineering industries the idea that we made mistakes 35 years ago and that modern designs are better is uncontroversial, but for some reason in computing we have rose-colored glasses for the early days of Unix. Everyone wants to blame the compiler authors because they don't want to admit C has flaws!)
These issues don't necessarily apply to other languages. I believe that if you design a language optimally you don't need to fall back on undefined behavior to get good performance. But I don't believe C is that language, and I think attempts to dial back undefined behavior without changing C are missing the point.
Another question: how do "unsafe Rust" (ie. Rust inside unsafe blocks) and C compare in this sense? If satisfying the borrow checker for some bit of code is too hard, is writing in unsafe Rust better/safer than C?
>• Integer overflow
> ◦ Overflow is considered "unexpected" behavior and is always user-error, unless the wrapping primitives are used. In non-optimized builds, the compiler will insert debug checks that panic on overflow, but in optimized builds overflow instead results in wrapped values. See RFC 560 for the rationale and more details.
(also important to note that when they say "RFC 560", they are referring to Rust RFCs and not IETF RFCs)
C makes a lot of things undefined that could be implementation defined even on weird hardware. That would considerably reduce the gap between customary C and standard C for "normal" hardware while letting the oddball machines do what they need to do and document it.
I don't know if the compiler implementors would stand for it, though.
I wouldn't without solid proof that performance won't regress.
The thing that's often forgotten in these discussions is that compiler authors listen to their customers, and what they do reflects what their customers want. Compiler developers don't implement optimizations because they're scheming language lawyers looking for ways to break programs. They implement optimizations because their customers file bugs saying that a competing compiler optimized their program a certain way and wondering why all compilers don't do the same.
It's instructive to go back and look at mailing list and message board discussions at the height of the GCC/LLVM/MSVC/ICC competition. The performance wars were intense.
Undefined behavior allows the compiler to handle those cases as errors (e.g. print stack trace and crash). Or to perform static analysis. Or, to do something predictable.
In practice, no existing compiler that I'm aware of issues a stack trace on undefined behavior at runtime.
Instead, the compiler reasons from the perspective that if a section of code would have undefined behaviour under some set of circumstances, it will assume that those circumstances will never occur, and optimize accordingly (with sometimes surprising results).
The term you're looking for here is "implementation defined".
It's called valgrind.
Try running a serious production program in it and then tell us about how trivial those UB-based optimizations are.
UB is just the nature of the beast.
If you have an object which is ten bytes long, like:
uint8_t o[10];
...then it's legal to construct pointers to o[0] through o[10] --- not o[9]; you can create a pointer to the byte immediately after the object --- and nowhere else. Like, it's not even legal to calculate one, let alone dereference it.I used this to make a prototype compiler from C to Javascript/Perl/Lua, where each C pointer was represented as a tuple of (array, offset). Pointer arithmetic worked inside objects; pointer arithmetic between objects wasn't supported. Worked nicely.
When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand. [...] If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.
[1]: http://stackoverflow.com/questions/18186987/decrementing-a-p...
I believe that C99 added the ability to losslessly cast from a pointer to a uintptr_t and back again, but, IIRC, the compiler didn't have to support this in C89.
Given two distinct objects, x and y, (&x == &y) is meaningful, but (&x < &y) isn't particularly. (Except that sometimes it would be convenient to have a consistent total ordering on addresses, something that C doesn't define.)
For example, consider x86 segments. Is there a reason why you would use negative offsets? Given a segment address, the number of representable values is identical whether the offset is strictly positive, or negative with a shifted segment address (assuming two's compliment).
More broadly, while I'm not sure if it really fits in C, it would be cool to see some kind of lambda function support, along the lines of Apple's "blocks" extension.
I'd like to see blocks/lambdas standardized, but I'd want to see a second implementation first. I also hope for better properties than those of the Apple blocks extension; I realize C does manual resource management, but the lifetime issues in blocks seem particularly worse even by C standards.
Instead of moving to C++, I'd rather allow mixed objects in a more sensible language (eg. linking with objects written in OCaml).
Operator overloading is very important for generating the fastest code possible, too. It would be hard to get the benefits of a library like Eigen which can automatically use SSE to optimize maths expressions in C.
However that does point to the one problem with attribute((cleanup)). If you consider code like this (using the systemd macros):
int f (void)
{
_cleanup_free char *s1;
_cleanup_free char *s2;
s1 = strdup ("foo");
if (!s1) return -1; /* crash */
s2 = strdup ("bar");
if (!s2) return -1;
// ...
return 0;
}
If strdup fails, it'll crash at the place I marked because it will try to free the uninitialized pointer s2.To get around that you have to initialize everything to NULL (since free(NULL) is legal).
The problem then becomes that you end up "over-initializing" and "over-freeing". It would be nice to have a version of attribute((cleanup)) that would eliminate the call to the cleanup function if the pointer is NULL (or uninitialized?). But then you're relying on the compiler to do some kind of dataflow analysis, which is difficult in a standard (excludes simple compiler implementations), and may not even be possible because of the halting problem.
This is basically the reason why you wouldn't want to use cleanups in kernel code, because performance is paramount there and unnecessary cleanup calls wouldn't be welcome.
static inline void free(void *ptr)
{
if (!ptr)
return;
_free(ptr);
}
Then the compiler can easily inline this, omit the NULL check if it knows the pointer can't be NULL, or omit the whole thing if it knows the pointer is NULL. #include <stdlib.h>
int main()
{
free(NULL);
char *foo = malloc(42);
free(foo);
}
gcc -O1 compiles it to: 00000000004004f6 <main>:
4004f6: b8 00 00 00 00 mov $0x0,%eax
4004fb: c3 retq
4004fc: 0f 1f 40 00 nopl 0x0(%rax)EDIT: I super misread that, my bad.
int f (void) {
_cleanup_free char *s1 = strdup ("foo");
if (!s1) return -1;
_cleanup_free char *s2 = strdup ("bar");
if (!s2) return -1;
// ...
return 0;
} #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define autofree __attribute((cleanup(autofree_func)))
#define PASS 0
#define FAIL 1
void autofree_func(void *ptr_ptr) {
void *ptr = * (void **) ptr_ptr;
printf("%s(%p)\n", __func__, ptr);
free(ptr);
}
int test(int fail1, int fail2) {
printf("\n%s(%s, %s):\n", __func__,
fail1 ? "FAIL" : "PASS",
fail2 ? "FAIL" : "PASS");
autofree char *s1 = strdup("foo");
if (fail1) return -1;
printf("s1: '%s' (%p)\n", s1, s1);
autofree char *s2 = strdup("bar");
if (fail2) return -1;
printf("s2: '%s' (%p)\n", s2, s2);
return 0;
}
int main(/* int argc, char **argv */) {
test(PASS, PASS);
test(PASS, FAIL);
test(FAIL, FAIL);
return 0;
}
nate@skylake$ cc -Wall -Wextra -Wconversion -O3 scoping.c -o scopingnate@skylake$ ./scoping
test(PASS, PASS):
s1: 'foo' (0x2056010)
s2: 'bar' (0x2056030)
autofree_func(0x2056030)
autofree_func(0x2056010)
test(PASS, FAIL):
s1: 'foo' (0x2056010)
autofree_func(0x2056030)
autofree_func(0x2056010)
test(FAIL, FAIL):
autofree_func(0x2056010)I guess it is if you are thinking of this as C++ RAII destructors, which is indeed one very useful case.
It's not useful in some other cases. Like imagine that you are writing the C equivalent of a constructor. If you initialize half your members and then run out of memory, you want to uninitialize only the members you managed to initialize already. attribute((cleanup)) doesn't help with this case.
out_error:
dtor(self);>N2012 2016/03/10 Sewell, Clarifying the C Memory Object Model
>N2013 2016/03/10 Sewell, C Memory Object and Value Semantics: The Space of de facto and ISO Standards
>N2014 2016/03/10 Sewell, What is C in Practice? (Cerberus Survey v2): Analysis of Response
>N2015 2016/03/10 Sewell, What is C in practice? (Cerberus survey v2): Analysis of Responses - with Comments
It's useful to print binary representations of things (debugging, conversion, etc...). A number of libc implementations support it as an extension. Forcing people to re-implement it outside of printf is a bit obnoxious considering how simple it is, and that there are already hexadecimal and octal conversions.
> The Standard is currently written in troff, which is subject to increasing bit rot as the tools for formatting it evolve.
What exactly is bit-rotting? There are multiple maintained troff's out there, and if they are breaking backward compatibility regularly it's news to me.
For example, having .PCX files in some project today instead of .PNG could be considered a form of bit rotting, because less and less tools support .PCX
Currently, the only compiler I'm aware of that optimizes a bswap correctly is LLVM, which leaves a lot of code in the cold Aside from requiring people to write their own bswap, which is fairly maligned [1] for good reason, It leaves out a reasonably important optimization or potentially requires excess maintenance (platform libs, intrinsics, etc...) for people who do need portable code (the main reason for bswap).
I think languages are starting to take the place of platform independent interfaces, and my personal opinion is that bswap is fairly low hanging fruit. Given that one of the stated goals of C is portability, I think it fits well within the mandate.
[1] http://commandcenter.blogspot.com/2012/04/byte-order-fallacy...
Just because an _optional_ feature is impractical on a few niche architectures doesn't mean it's a mistake.
That isn't why it's a mistake. Arrays and the stack don't mesh well together, especially arrays that aren't bounded by anything other than the width of the integral type used for the VLA length.
Are there any hosted systems (systems that can support the standard library) on which VLAs are difficult to implement?
The thing that makes VLAs hard to implement is that on some embedded and special purpose processors the stack is actually a physical, fixed-size location on the chip. I recall, but can't seem to find, some architecture that didn't use an explicit stack pointer.
1. Itanium has 2, but for purposes of VLAs it may as well have a conventional stack.
Why do you believe they are a mistake?
VLAs make it impossible to handle a failure to allocate: there is no interface to indicate what the program should do when it happens. You can only follow the declaration of a VLA with code that assumes that the allocation succeeded.
On the other hand, if you're currently allocating a fixed-size array that's big enough to hold, say, 1024 elements (of which you're only going to use some initial subset), then replacing it with a VLA that's the exact size you need (<= 1024) will be an improvement.
malloc() is supposed to safely tell you whether an allocation succeeded or failed, but in practice it doesn't do so reliably. On Linux, by default, it can allocate a huge chunk of address space, and then fail (with no way to handle the failure) when you try to access it. When that happens, it can invoke the "OOM killer", which can kill other processes.
Having a mechanism to abstract this might be nice, but these are things people should hopefully be aware of before using VLAs. std::dynarray has been a bit polarizing though.
I think we can leave VLAs in the "trust the programmer" category. Very handy with bounded sizes; it keeps you from wasting all those warm cache lines.
Apparently the lack of a frame pointer on ARM means that tracking dynamically sized stack frames is a problem.
(b) Use? yes, but only when I'm not using a library that relies on the parts I don't want. Implement? not really, since users expect their compilers to conform to the standards. Rely on for portable code? no, since not all compilers support C99 and many of those that do actually only support some subset.
For now, I'm using C89 with my own safe string routines and a configure script to generate the bits of stdint.h that I need.
Anyway, like I said, I'm often stuck supporting older compilers, too. So it might still be some time before I can "upgrade" to C99.
...and digraphs & trigraphs had to do with different terminals/text editors that didn't have keys/characters for certain language symbols. Last I checked it wasn't really about internationalization, but rather the rather insane variance of character sets just for english (like EBCDIC).
For trigraphs see the Rationale at http://www.lysator.liu.se/c/rat/b.html#2-2-1-1
I thought C90 was essentially just a reformatting of C89. Interestingly, the ANSI C page agrees with me: https://en.wikipedia.org/wiki/ANSI_C#C95
__attribute__((cleanup))
__attribute__((overloadable))
… are now indispensable to me.Cleanup I use for both object lifetime management (when it fits that model), and lock management. Typically a "get access" type of function will take the lock and return the pointer to the object. Then when that pointer goes out of scope the lock can be freed.
overloadable is perfect for keeping names under control. Consider if you have a bunch of structs for encoding various bit arrays. Maybe 32, 64, and 1024 bit long. Each has functions for working on it, so you either prefix all the functions with bit32_, bit1024_ or you pick a letter suffix, e.g. bitset(), bitsetl(), bitseth() (huge?), and try to make the programmer remember them all. With overloadable functions I get to use the same simple name for the operation and which implementation you use underneath is irrelevant.
I think I'd like to go one step further and sugar up the syntax so…
variable.func(a,b) <----> func( variable,a,b)
… but Mr. Stroustrup might call me names. But darn it, sometimes you want to think of your objects in different ways. I'd be using lisp if I wanted everything the same. (Actually, I suppose Kernel, and laughing at the lisp users who think they have uniformity.) It doesn't have to be ".", go ahead an create an "=>" notation if that helps with the ambiguity of structure reference. (No dynamic dispatch here. Just leverage overloadable.)And here is the big one: Thread Safety Analysis. This has the potential to eliminate huge swathes of runtime problems in multithreaded code and even calling convention problems in single threaded code.
In a nutshell, you can annotate your program to make statements about objects. You can say an operation ACQUIRE's an object (takes a lock), and that another operation RELEASEs an object. Then you can declare that other options REQUIRE that the object be in the ACQUIREd state. There is more, such as declaring that a the execution is currently in a state and then restricting its ability to perform operations based on that.
The current implementation is probably bit rotting in gcc, and the clang version seems to be missing some important bits to making it useful. The C++ end of it apparently works for Google with their Android coding conventions, but the C end of it doesn't quite do it for me. I think with perhaps letting a function declare that its return value is ACQUIRE()d I might be able to put it to use. (And fixing the bit where it doesn't play well with __attribute__((cleanup)) ).
But the working group has a '2' in the name. We have 4 years.
And one more huge one: PLEASE add a paragraph to the spec that says:
"undefined" is NOT license for some language lawyer compiler writer to kill your dog, burn your garden, erase your disk and return 42 from the main program.
Undefined should mean "we understand different architectures and compilers might prefer to do this differently, implement something SANE and warn the user that they are on squishy ground".
• blocks are pretty spiffy.
• nested functions, by which I mean I don't need access to the parent's variables, I just want to tuck this little function inside the namespace of my existing function because darn it, it's two lines long and I can give it a tiny name, and I don't have to stick it above my giant function comment header where I can't see it while I'm in the code that is using it. I just want to abstract these two lines that I need to do three times in this function. gcc has these, clang thinks it's hard in their world.
I would love for this to be in the standard, particularly if they come up with a nested function literal syntax. Having them would make things like mutation callbacks and foreach() so much cleaner.
Since the implementation is somewhat complex and system-specific, depends on memory that is both writeable and executable, and requires disabling security features on modern OSes, I doubt GCC style nested functions will make it into the standard.
> I think I'd like to go one step further and sugar up the syntax so…
> variable.func(a,b) <----> func( variable,a,b)
> … but Mr. Stroustrup might call me names.
http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0251r0.... … but that's C++."Demonstrable" seems to be a bit of weasel-wording there, but for the life of me I don't see how you make C demonstrably safe and secure without ending up with something that is almost, but not quite, entirely unlike C.
I agree with you of course that C will never be memory-safe either in theory or in practice while still remaining C. (The same goes for C++, despite popular belief.)
To be more precise, I would
- Quit separating definition and implementation (.h & .c vs. the singular .go)
- Implement multiple returns
- Replace #include with something a bit closer to Go's import (Package name and in-body identifier are decoupled)
- Methods on any user-defined type
- Finally, something namespace-like because
library_thing_return_t *library_thing_doing_something_else(libraty_thing_param_t *in)
seems unsustainable to me.If you choose a long prefix to disambiguate your library functions, then it gets annoying, but you don't have to use long prefixes. "sdl" and "ao" and "X" and "gtk" are great examples of short vendor prefixes for identifiers. In Go, since you brought it up, you have to reference identifiers from foreign packages with a prefix anyway. Go does let you change the prefix if it would otherwise conflict with your code, but because of syntactical concerns, it's more likely to conflict in the first place.
Which brings me to the interesting thing about C's lack of namespacing: it means that global identifiers look the same everywhere. This enables you to safely use grep and sed on global identifiers. It's not 100% foolproof because you can shadow a global with a local, and the identifier could appear in a string or comment, but if you're disciplined with the naming conventions of your globals, it should be very unlikely that a local accidentally shadows a global, and if a global identifier's name appears in a string, or especially in a comment, it's probably actually refering to that identifier.
This means that a language design decision turns simple refactoring features from a somewhat complex tool that needs compiler integration into "just use sed".
Why are header files necessary for dynamic linking?
To name one of dozens of examples, Java has dynamic linking and has no header files.
No it isn't. That's a completely orthogonal issue. The compiler is perfectly capable of generating the equivalent of a public header if the implementation has a way to specify visibility.