The C language has a set of outright reserved keywords. However, there is a much larger set of identifiers that are reserved or semi-reserved, whose use is at least strongly not recommended because they are used by the standard library or various system headers, or may be so used in future, etc; there is a comprehensive though not exhaustive list of those here: https://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html
The set of such names is much too large to be feasible to enumerate.
Looking at it from the perspective of using C as a compilation target, I'm looking for the reverse: a set of names I can generate, that are guaranteed to be not reserved, to be free for application use.
Clearly this requirement could be effectively met as far as it goes by prepending a UUID to every name, but there is an additional requirement that the generated code be as amenable as possible to eyeball debugging, so the namespace should be as simple as possible, e.g. if all names are to have a common prefix, that prefix should be as short as possible.
What's the simplest way to characterize a set of names that are guaranteed, or failing that highly likely, to be free for application use? For example, would it be safe to use arbitrary names prefixed with x_ or suchlike?
Most C libraries provide feature-selection macros, which allow you to specify which version of the interface you are using. If you set _POSIX_C_SOURCE and _XOPEN_SOURCE before including any system headers on Linux or UNIX, your system libraries will not declare any identifiers that future versions of UNIX might define. (In theory, setting either one by itself should suffice, but it’s good defensive coding to set both, as this will prevent one or the other from being set inconsistently by someone else.) On Windows, you would declare NTDDI_VERSION and _WIN32_WINNT.
The C Standard Library only provides feature-test macros, not macros that let you choose an interface, but compliers support a flag such as -std=c20, and you should set this in your build scripts. This should disable any new keywords or identifiers that get added to the language in the future.
If you depend on a specific version of a library, and are worried that changes to its header files could break your code, you can put a copy of the headers (and to be absolutely certain, the library itself) in your project tree. If the library is open-source, making a note of which version you used should let anyone else download the right version. Otherwise, you’re at the mercy of its maintainers.
Do not declare _BSD_SOURCE or _GNU_SOURCE if this is a concern for you! Linux headers without glibc bindings, such as <linux/module.h>, generally don’t have this kind of versioning.
Some languages have much more robust solutions for this, such as cabal and stack for Haskell or cargo for Rust.
Related
I am working on an open source C driver for a cheap sensor that is used mostly for Arduino projects. The project is set up in such a way that it is possible to support multiple platforms outside the Arduino ecosystem, like the Raspberry Pi.
The project is set up with a platform.h file, with the intention of having different implementations of this header file. Like the example below:
platform.h
platform_arduino.c
platform_rpi.c
platform_windows.c
There is this (Cross-Platform C++ code and single header - multiple implementations) Stack Overflow post that goes fairly in depth in how to handle this for C++ but I feel like none of those examples really apply to this C implementation.
I have come up with some solutions like just adding the requirements for each platform at the top of the file.
#if SOME_REQUIREMENT
#include "platform.h"
int8_t t_open(void)
{
// Implementation here
}
#endif //SOME_REQUIREMENT
But this seems like a clunky solution.
It impacts readability of the code.1
It will probably make debugging conflicting requirements a nightmare.
1 Many editors (Like VS Code) try to gray out code which does not match requirements. While I want this most of the time, it is really annoying when working on cross-platform drivers. I could just disable it for the entirety of the project, but in other parts of the project it is useful. I understand that it could probably be solved using VS Code thing. However, I am asking for alternative methods of selecting the right file/code for the platform because I am interested in seeing what other strategies there are.
Part of the "problem" is that support for Arduino is the primary focus, which means it can't easily be solved with makefile magic. My question is, what are alternative ways of implementing a solution to this problem, that are still readable?
If it cannot be done without makefile magic, then that is an answer too.
For reference, here is a simplified example of the header file and implementation
platform.h
#ifndef __PLATFORM__
#define __PLATFORM__
int8_t t_open(void);
#endif //__PLATFORM__
platform_arduino.c
#include "platform.h"
int8_t t_open(void)
{
// Implementation here
}
this (Cross-Platform C++ code and single header - multiple implementations) Stack Overflow post that goes fairly in depth in how to handle this for C++ but I feel like none of those examples really apply to this C implementation.
I don't see why you say that. The first suggestions in the two highest-scoring answers are variations on the idea of using conditional macros, which not only is valid in C, but is a traditional approach. You yourself present an alternative along these lines.
Part of the "problem" is that support for Arduino is the primary focus, which means it can't easily be solved with makefile magic.
I take you to mean that the approach to platform adaptation has to be encoded somehow into the C source, as opposed to being handled via the build system. Frankly, this is an unusual constraint, except inasmuch as it can be addressed by use of the various system-identification macros provided by C compilers of interest.
Even if you don't want to rely specifically on makefiles, you should consider attributing some responsibility to the build system, which you can do even without knowing specifically what build system that is. For example, you can designate macro names, such as for_windows, etc that request builds for non-default platforms. You then leave it to the person building an instance of the driver to figure out how to configure their tools to provide the appropriate macro definition for their needs (which generally is not hard), based on your build documentation.
My question is, what are alternative ways of implementing a solution to this problem, that are still readable?
If the solution needs to be embodied entirely in the C source, then you have three main alternatives:
write code that just works correctly on all platforms, or
perform runtime detection and adaptation, or
use conditional compilation based on macros automatically defined by supported compilers.
If you're prepared to rely on macro definitions supplied by the user at build time, then the last becomes simply
use conditional compilation
Do not dismiss the first out of hand, but it can be a difficult path, and it might not be fully possible for your particular problem (and probably isn't if you're writing a driver or other code for a freestanding implementation).
Runtime adaptation could be viewed as a specific case of code that just works, but what I have in mind for this is a higher level of organization that performs runtime analysis of the host environment and chooses function variants and internal parameters suited to that, as opposed to those choices being made at compile time. This is a real thing that is occasionally done, but it may or may not be viable for your particular case.
On the other hand, conditional compilation is the traditional basis for platform adaptation in C, and the general form does not have the caveat of the other two that it might or might not work in your particular situation. The level of readability and maintainability you achieve this way is a function of the details of how you implement it.
I have come up with some solutions like just adding the requirements for each platform at the top of the file. [...] But this seems like a clunky solution.
If you must include a source file in your build but you don't want anything in it to actually contribute to the target then that's exactly what you must do. You complain that "It will probably make debugging conflicting requirements a nightmare", but to the extent that that's a genuine issue, I think it's not so much a question of syntax as of the whole different code for different platforms plan.
You also complain that the conditional compilation option might be a practical difficulty for you with your choice of development tools. It certainly seems to me that there ought to be good workarounds for that available from your tools and development workflow. But if you must have a workaround grounded only in the C language, then there is one (albeit a bad one): introduce a level of preprocessing indirection. That is, put the conditional compilation directives in a different source file, like so:
platform.c
#if defined(for_windows)
#include "platform_windows.c"
#else
#if defined(for_rpi)
#include "platform_rpi.c"
#else
#include "platform_arduino.c"
#endif
#endif
You then designate platform.c as a file to be built, but not (directly) any of the specific-platform files.
This solves your tool-presentation issue because when you are working on one of the platform-specific .c files, the editor is unlikely to be able to tell whether it would actually be included in a build or not.
Do note well that it is widely considered bad practice to #include files containing function implementations, or those not ending with an extension conventionally designating a header. I don't say otherwise about the above, but I would say that if the whole platform.c contains nothing else, then that's about the least bad variation that I can think of within the category.
I implemented a SV module which contains soft constraints. However, as far as I know soft constraints are only supported since 1800-2012 standard. Therefore I would like to add an alternative implementation in case a simulator is used that only supports older standard versions.
Is there a way to retrieve this information with a system task or pre-processor directive in such a way:
if($get_version_specifier == "1800-2012")
// do fancy stuff with soft constraints
else
// alternative fancy stuff
I already found an option for a similar problem by using begin_keywords, end_keywords, but I think that would not solve my issue since it only defines the set of keywords for a specific standard. And if the simulator does not support this version I guess only an error would occur.
Thanks in advance!
sebs
The problem you ask about is more complicated than it seems. Different features of SystemVerilog are implemented by different versions of tool; sometimes before the standard is released, sometimes after. I do know that some tools supported soft constraints before the release of the 1800-2012 standard, and no commercial tool that I know of has yet to support operator overloading, which was in the first IEEE 1800-2005 standard.
A better solution would be to define a set of macros like USE_SOFT_CONSTRAINTS for features that do not have universal support. Then you can include a common featureset.svh file that defines the feature set you want to use. Another good practice is to DOCUMENT the reason you added a specific feature macro (i.e the tool version you were using that didn't support the feature and why you decided it was worth the effort to implement both branches of the code).
As far as I know, there isn't any "standard" way of getting the version of the standard you are using. C++ had a similar problem before the 2011 release (see here). One answer there states that different compilers added different proprietary defines (something like the INCA macro set for the Incisive simulator). You'll have to ask your simulator vendor if a define for the version of the SV standard exists (something like SV2012_OR_GREATER).
Cadence, for example, has something like this for Specman, so if they're consistent they might have this for SystemVerilog as well. Assuming such a thing exists, you could have:
`ifdef SV_2012_OR_GREATER
// do fancy stuff with soft constraints
`else
// alternative fancy stuff
`endif
Bonus: Soft constraints are a declarative construct, so I don't see how you could use an if block to decide whether to use them or not (unless maybe if it's an if inside a constraint block). Also, I'm not sure whether you're able to truly emulate soft constraints in any way, however fancy your approach is, so I don't know if it really makes sense to try.
In regards to the thread safe functions in newer versions of the C standard library, is there a cross-platform way to tell if these are available via pre-processor definition? I am referring to functions such as localtime_r().
If there is not a standard way, what is the reliable way in GCC? [EDIT] Or posix systems with unistd.h?
There is no standard way to test that, which means there is no way to test it across all platforms. Tools like autoconf will create a tiny C program that calls this function and then try to compile and link it. It this works, looks like the function exists, if not, then it may not exist (or the compiler options are wrong and the appropriate CFLAGS need to be set).
So you have basically 6 options:
Require them to exist. Your code can only work on platforms where they exist; period. If they don't exist, compilation will fail, but that is not your problem, since the platform violates your minimum requirements.
Avoid using them. If you use the non-thread safe ones, maybe protected by a global lock (e.g. a mutex), it doesn't matter if they exist or not. Of course your code will then only work on platforms with POSIX mutexes, however, if a platform has no POSIX mutexes, it won't have POSIX threads either and if it has no POSIX threads (and I guess you are probably using POSIX threads w/o supporting any alternative), why would you have to worry about thread-safety in the first place?
Decide at runtime. Depending on the platform, either do a "weak link", so you can test at runtime if the function was found or not (a pointer to the function will point to NULL if it wasn't) or alternatively resolve the symbol dynamically using something like dlsym() (which is also not really portable, but widely supported in the Linux/UNIX world). However, in that case you need a fallback if the function is not found at runtime.
Use a tool like autoconf, some other tool with similar functionality, or your own configuration script to determine this prior to start of compilation (and maybe set preprocessor macros depending on result). In that case you will also need a fallback solution.
Limit usage to well known platforms. Whether this function is available on a certain platform is usually known (and once it is available, it won't go away in the future). Most platforms expose preprocessor macros to test what kind of platform that is and sometimes even which version. E.g. if you know that GNU/Linux, Android, Free/Open/NetBSD, Solaris, iOS and MacOS X all offer this function, test if you are compiling for one of these platforms and if yes, use it. If the code is compiled for another platform (or if you cannot determine what platform that is), it may or may not offer this function, but since you cannot say for sure, better be safe and use the fallback.
Let the user decide. Either always use the fallback, unless the user has signaled support or do it the other way round (which makes probably more sense), always assume it is there and in case compilation fails, offer a way the user can force your code into "compatibility mode", by somehow specifying that thread-safe-functions are not available (e.g. by setting an environment variable or by using a different make target). Of course this is the least convenient method for the (poor) user.
The compiler (Visual C++) always corrects me when I type flushall();
warning C4996: 'flushall': The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: _flushall.
They both work for me, is there any difference?
This warning is nonsensical; flushall is not POSIX, and to my knowledge, never was. The correct ISO C way to flush all open stdio streams is fflush(NULL);.
To shed some more light on the issue, I believe this is a generic warning message Visual C uses for functions which are not part of the C standard, but which their standard library has for some minimal degree of compatibility. Most of these functions are things like open and read, and correspond in some vague way to the POSIX functions by the same name, and can be thought of as the lower-level primitives underlying stdio, etc. Some, like flushall, are not actually from POSIX, but just based on oddball functions copied from various legacy unices.
Since these functions are not actually part of the C (or C++) standards, and the C standard (and possibly also the C++ standard...?) reserves for the application identifiers which are not explicitly defined or reserved in the standard, it's non-conformant to expose in the standard headers names like open or flushall. POSIX gets around this by using feature test macros by which an application can make the namespace guarantees it wants from the implementation explict, but Microsoft instead took an approach of deprecating the POSIX-like names and offering an alternative set of functions, prefixed with underscores, which makes them part of the namespace reserved for the implementation. The warning is recommending that you use these Microsoft-specific names, so that your code would still compile if you turned on the options (probably controlled by macros of compiler switches) for a strict namespace conformance in the headers.
Unfortunately, writing code the Microsoft-recommended way with the underscore-prefixed names renders your code completely non-portable. Since these names are in the namespace reserved for the implementation, other platforms might happen to use the same names for functions which do completely different things, and being as they're reserved, you would not be allowed to replace or #define around them to remap them to other functions.
In short: I would find the warning flag that's generating this warning, and turn it off. Along with the one that spews FUD that such-and-such standard function is "insecure" and you should use the _s-suffixed function instead. But if the only function you need is fflushall(), simply replace it with fflush(NULL); and you're good to go.
This goes back to late 1988, back when Dave Cutler's team started development on Windows NT. The design goal was to create an operating system with three distinct api layers, Winapi, OS/2 and POSIX. POSIX was very new back then, the very first version was released in 1988 as well. It got included because it was listed as a requirement in a USA government standard, FIPS 151-2.
Back then the Unix Wars started to get serious. Pretty vicious with intentional differences in competing implementations. Main combatants were the Open Software Foundation, an industry group that feared Sun's increasing dominance and Unix International, a group around AT&T. That dust didn't settle until 1993 and the formation of the COSE alliance, now Open Group.
This makes the exact origin of flushall() pretty hard to trace, it was very much the wild wild west back then. Microsoft was also in the C compiler business so of course they released the tools to build programs that ran on the POSIX layer, including headers that declared the functions. So surely the time that flushall came about in their toolset. I don't have a copy anymore to check, destroyed by a leaky roof.
The NT Posix and OS/2 api layers never gained much traction, significantly overshadowed by Win32's runaway success. They were entirely dropped in 2001 with the XP release. Which, I think, was also around the time that they started deprecating the original Posix function names. Clearly it didn't make much sense anymore to keep these declarations around when they were no longer supported by the operating system. The Posix names are fairly awkward, short lower-case names in the global namespace cause many accidents.
They were however never completely removed, still documented to this day. Which is why you got the warning. Rather then choosing, do favor standard C library names, fflush().
I'm working with an old C code that still has a few dusty corners. I'm finding a lot of #ifdef statements around that refer to operating systems, architectures, etc. and alter the code for portability. I don't know how many of these statements are still relevant today.
I know that #ifdef isn't the best idea in certain circumstances, and I'll be sure to fix that, but what I'm interested in here is what's being tested.
I've listed them below. If you could tell me if any of them are definitely useful in this day and age, or if the machines or OSs with which they're associated have long since expired, that would be great. Also, if you know of any central reference for these, I'd love to hear about it.
Thanks in advance,
Ross
BORLANDC
BSD
CGLE
DRYRUN
HUGE
IBMPC
MAIN
M_XENIX
OPTIMIZED
P2C_H_PROTO
sgi
TBFINDADDREXTENDED
UNIX
vms
__GCC__
__GNUC__
__HUGE__
__ID__
__MSDOS__
__TURBOC__
Here you are.
You are coming from the wrong direction. Instead of asking what code can be safely deleted, you should ask - what code have to stay.
Find out what platforms have to be supported and delete everything that is not defined in any of them. You'll get yourself cleanest code possible that is still guaranteed to work.
What context is this code being used?
If it's a library other people outside your organization are using, you shouldn't touch this stuff unless you're releasing a new version and explicitly removing support for some OSs. In the latter case, you should remove all the relevant IFDEF code as part of making a new release, and should be explicit about what you are removing.
If it's a library people inside your organization are using, you should ask those people what you can remove, not us.
If it's code being used very narrowly (i.e. you control its use directly), you can, if you wish, safely remove any sort of compiler portability, since you are only using one compiler.
You're asking the wrong people: It's your users (or potential users) who decide what's still useful, not us. Start by finding out what platforms you need to support, and then you can find out what's not needed.
If, for example, you don't need to support 16-bit systems, you can dispense with __HUGE__, __MSDOS__, and __TURBOC__.
Any #ifdef based on arbitrary preprocessor definitions provided by the implementation is outdated - especially those which are in the namespace reserved for the application, not the implementation, as most of those are! The correct modern way to achieve this kind of portability is to either detect the presence of different interfaces/features/behavior with a configure script and #define HAVE_FOO etc. based on that, directly test standard preprocessor defines (like UINT_MAX to determine integer size), and/or provide prebuilt header files for each platform you want to support with the appropriate HAVE_FOO definitions.
The old-style "portability" #ifdefs closely coupled knowledge of every single platform all over your source, making for a nightmare when platforms changed and adopted new features, behaviors, or defaults. (Just imagine the mess of old code that assumes Windows is 16bit or Linux has SysV-style signal()!) The modern style isolates knowledge of the platform and allows the conditional compilation in your source files to depend only on the presence/absence/behavior of the feature it wants to use.
Code that is annotated like that can in fact be quite difficult to maintain. You could consider to look into something like autotools or alike to configure your sources for a particular architecture.