4 ms·
A lot C programmers prefer to keep structures within the C source file ("module"), as a poor man's encapsulation. For example: component.h: struct obj;
by rmind 6y ago
A lot C programmers prefer to keep structures within the C source file ("module"), as a poor man's encapsulation. For example:
component.h:
struct obj;
typedef struct obj obj_t;
obj_t *obj_create(void);
// .. the rest of the API
component.c:
struct obj {
int status;
// .. whatever else
};
obj_t *
obj_create(void)
{
return calloc(1, sizeof(obj_t));
}
However, as the component grows in complexity, it often becomes necessary to separate out some of the functionality (in order to re-abstract and reduce the complexity) into a another file or files, which also operate on "struct obj". So, we move the structure into a header file under #ifdef __COMPONENT_PRIVATE (and/or component_impl.h) and sprinkle #define __COMPONENT_PRIVATE in the component source files. It's a poor man's "namespaces".
Basically, this boils down to the lack namespaces/packages/modules in C. Are you aware of any existing compiler extensions (as a precedent or work in that direction) which could provide a better solution and, perhaps, one day end up in the C standard?
P.S. And if C will ever grow such feature, I really hope it will NOT be the C++ 'namespace' (amongst many other depressing things in C++). :)
- msebor 6y agoThe ELF visibility attributes solve the part of the problem at the binary level (by hiding private library APIs from the application). The rest should be doable by structuring the project sources and headers in a suitable way.
- loeg 6y agoELF is very much not part of the C standard.
- pascal_cuoq 6y agoI am sorry I do not have an answer to your question. It's a very valid one and I would be interested in any pointer to an answer. What I can say while we are on the subject, is that I have seen C code (most often C code that started its life in the 1990s, to be fair) that instead of showing an abstract struct in the public interface, showed a different struct definition. Please don't do this. Yes, when compiling nowadays, eventually every compilation unit ends up as object files passed to a linker that doesn't know about types, but this is undefined behavior. It makes it difficult to find undefined behavior in the rest of the code because there is a big undefined behavior right in the middle of it.
- beefhash 6y agoWait, doesn't this mean that the BSD sockets API is inherently dependent on UB, casing different socket types to each other and sometimes only using the first few members, or am I misunderstanding you?
- pascal_cuoq 6y agoYes and no. The thing I am describing is when you link a compilation unit using: struct internal_state { int dummy; } state; with another compilation unit that defined the same state differently: struct internal_state { int actual_meaningful_member_1; unsigned long actual_meaningful_member_2; } state; As far as I know, BSD socked do not do this. Zlib was doing this (https://github.com/pascal-cuoq/zlib-fork/blob/a52f0241f72433b69fd558100a32d927d9571e20/zlib.h#L1740 https://github.com/pascal-cuoq/zlib-fork/blob/a52f0241f72433... ), but I have had the privilege of discussing this with Mark Adler, and I think the no-longer-necessary hack was removed from Zlib. BSD sockets probably have a different kind of UB, related to so-call “strict aliasing” rules, unless they have been carefully audited and revised since the carefree times in which they were written. I am going to have to let you read this article for details (example st1, page 5): https://trust-in-soft.com/wp-content/uploads/2017/01/vmcai.pdf https://trust-in-soft.com/wp-content/uploads/2017/01/vmcai.p...
- loeg 6y agoBSD sockets are weird in that the first struct's (sockaddr) size wasn't big enough, so APIs all take a nominal pointer to sockaddr but may require larger storage (sockaddr_storage) depending on the actual address. /* * Structure used by kernel to store most * addresses. */ struct sockaddr { unsigned char sa_len; /* total length */ sa_family_t sa_family; /* address family */ char sa_data[14]; /* actually longer; address value */ }; /* * RFC 2553: protocol-independent placeholder for socket addresses */ #define _SS_MAXSIZE 128U #define _SS_ALIGNSIZE (sizeof(__int64_t)) #define _SS_PAD1SIZE (_SS_ALIGNSIZE - sizeof(unsigned char) - \ sizeof(sa_family_t)) #define _SS_PAD2SIZE (_SS_MAXSIZE - sizeof(unsigned char) - \ sizeof(sa_family_t) - _SS_PAD1SIZE - _SS_ALIGNSIZE) struct sockaddr_storage { unsigned char ss_len; /* address length */ sa_family_t ss_family; /* address family */ char __ss_pad1[_SS_PAD1SIZE]; __int64_t __ss_align; /* force desired struct alignment */ char __ss_pad2[_SS_PAD2SIZE]; };
- defectbydesign 6y agoThere are already "Name Spaces" in C and modules are actually object files or libraries. You can spread components in as many object files or libraries as you wish. IMHO it's not a C related problem but a code design one. Write libraries (with headers) only if you need to share the code but if you're not sure about that just include it for your specific program. There is no shame to include local files containing declarations and definitions. I think it is a misconception from C programmers to write headers for local purpose.