- Porting polymorphic classes from C++ to C - 2 Updates
- Porting C++ code to C - 4 Updates
- Worst thing about 2020 C++ - 4 Updates
Frederick Gotham <cauldwell.thomas@gmail.com>: Aug 18 06:32AM -0700 At the moment I'm taking the encryption technique I've coded in multithreaded C++ code, and porting it to single-threaded C for use on a microcontroller. To demonstrate how I'm handling so-called 'polymorphic' function calls, let's start off with this C++ program: #include <cstddef> /* size_t */ #include <cstring> /* strlen, strcpy */ #include <iostream> /* cout, endl */ class Parser { protected: char *_name; bool _should_tell_name; public: Parser(char const *const arg, bool const shareable) : _should_tell_name(shareable) { _name = new char[std::strlen(arg)]; std::strcpy(_name,arg); std::cout << "Object constructed" << std::endl; } virtual ~Parser(void) { delete [] _name; std::cout << "Object destructed" << std::endl; } virtual bool CanTellName(void) const { return _should_tell_name; } virtual char const *GetName(void) const { return CanTellName() ? _name : "No Comment"; } }; int main() { Parser obj("Frederick", false); std::cout << obj.GetName() << std::endl; } Well the first thing I do to turn this into C is to change the included header filenames, and replace cout's and new's: #include <stddef.h> /* size_t */ #include <string.h> /* strlen, strcpy */ #include <stdlib.h> /* malloc, free */ #include <stdio.h> /* puts */ typedef int intbool; /* Because C90 doesn't have bool */ class Parser { protected: char *_name; intbool _should_tell_name; public: Parser(char const *const arg, intbool const shareable) : _should_tell_name(shareable) { _name = (char*)malloc(strlen(arg)); strcpy(_name,arg); puts("Object constructed"); } virtual ~Parser(void) { free(_name); puts("Object destructed"); } virtual intbool CanTellName(void) const { return _should_tell_name; } virtual char const *GetName(void) const { return CanTellName() ? _name : "No Comment"; } }; int main() { Parser obj("Frederick", true); puts(obj.GetName()); } Next I turn the class into a POD struct with a Vtable as follows, the following with compile with a C compiler: #include <stddef.h> /* size_t */ #include <string.h> /* strlen, strcpy */ #include <stdlib.h> /* malloc, free */ #include <stdio.h> /* puts */ typedef int intbool; /* Because C90 doesn't have bool */ struct Parser_Vtable { void (*Destructor)(void); intbool (*CanTellName)(void) /* const */ ; char const *(*GetName)(void) /* const */ ; }; void Parser_Detail_Destructor (void); intbool Parser_Detail_CanTellName(void) /* const */ ; char const *Parser_Detail_GetName (void) /* const */ ; struct Parser_Vtable const g_parser_vtable = { &Parser_Detail_Destructor, &Parser_Detail_CanTellName, &Parser_Detail_GetName }; struct Parser { struct Parser_Vtable const *vtable; char *_name; intbool _should_tell_name; }; void Parser_Constructor(struct Parser *const this, char const *const arg, intbool const shareable) { this->_should_tell_name = shareable; this->_name = (char*)malloc(strlen(arg)); strcpy(this->_name,arg); puts("Object constructed"); } int main() { struct Parser obj; Parser_Constructor(&obj, "Frederick", 1 /*true*/); /* puts(obj.GetName()); - - What do we do here ? */ } Next I'll implement the three virtual functions in C: void Parser_Detail_Destructor(void) { struct Parser *const this = Pop_This(); free(this->_name); puts("Object destructed"); } intbool Parser_Detail_CanTellName(void) /* const */ { struct Parser const *const this = Pop_This_Const(); return this->_should_tell_name; } char const *Parser_Detail_GetName(void) /* const */ { struct Parser const *const this = Pop_This_Const(); /* I will explain this next line in just a minute */ return Virtcall(this,CanTellName)() ? this->_name : "No Comment"; } In order to get this all to work, here's my helper functions, and my macro "Virtcall" which is used for invoking a virtual method on an object: void *g_stack_of_this[16] = {0}; static void Push_This(void const *const arg) { void **p = g_stack_of_this + 0u; while ( 0 != *p ) ++p; *p = (void*)arg; /* Okay to discard const here if correct form of Pop_This is used */ } static void *Pop_This(void) { void *retval; void **q = g_stack_of_this + (sizeof g_stack_of_this / sizeof *g_stack_of_this) - 1u; while ( 0 == *q ) --q; retval = *q; *q = 0; return retval; } static void const *Pop_This_Const(void) { return (void const*)Pop_This(); } #define Virtcall(p, method) ( Push_This((p)) , ((p))->vtable->method ) The macro "Virtcall" yields a function pointer, so you can then put "()" after it to call the function with no arguments. This code I've written isn't threadsafe because there is only one "g_stack_of_this" for the entire process. On a newer C compiler (e.g. C11), you could use the "_Thread_local" keyword on this global array to make everything threadsafe. So anyway this is how I'm going about porting my C++ encryption technique to C. Here's the full code: #include <stddef.h> /* size_t */ #include <string.h> /* strlen, strcpy */ #include <stdlib.h> /* malloc, free */ #include <stdio.h> /* puts */ void *g_stack_of_this[16] = {0}; static void Push_This(void const *const arg) { void **p = g_stack_of_this + 0u; while ( 0 != *p ) ++p; *p = (void*)arg; /* Okay to discard const here if correct form of Pop_This is used */ } static void *Pop_This(void) { void *retval; void **q = g_stack_of_this + (sizeof g_stack_of_this / sizeof *g_stack_of_this) - 1u; while ( 0 == *q ) --q; retval = *q; *q = 0; return retval; } static void const *Pop_This_Const(void) { return (void const*)Pop_This(); } #define Virtcall(p, method) ( Push_This((p)) , ((p))->vtable->method ) typedef int intbool; /* Because C90 doesn't have bool */ struct Parser_Vtable { void (*Destructor)(void); intbool (*CanTellName)(void) /* const */ ; char const *(*GetName)(void) /* const */ ; }; void Parser_Detail_Destructor (void); intbool Parser_Detail_CanTellName(void) /* const */ ; char const *Parser_Detail_GetName (void) /* const */ ; struct Parser_Vtable const g_parser_vtable = { &Parser_Detail_Destructor, &Parser_Detail_CanTellName, &Parser_Detail_GetName }; struct Parser { struct Parser_Vtable const *vtable; char *_name; intbool _should_tell_name; }; void Parser_Constructor(struct Parser *const this, char const *const arg, intbool const shareable) { this->vtable = &g_parser_vtable; this->_should_tell_name = shareable; this->_name = (char*)malloc(strlen(arg)); strcpy(this->_name,arg); puts("Object constructed"); } int main() { struct Parser obj; Parser_Constructor(&obj, "Frederick", 1 /*true*/); puts( Virtcall(&obj,GetName)() ); Virtcall(&obj,Destructor)(); return 0; } void Parser_Detail_Destructor(void) { struct Parser *const this = Pop_This(); free(this->_name); puts("Object destructed"); } intbool Parser_Detail_CanTellName(void) /* const */ { struct Parser const *const this = Pop_This_Const(); return this->_should_tell_name; } char const *Parser_Detail_GetName(void) /* const */ { struct Parser const *const this = Pop_This_Const(); return Virtcall(this,CanTellName)() ? this->_name : "No Comment"; } Frederick |
Frederick Gotham <cauldwell.thomas@gmail.com>: Aug 18 11:20AM -0700 > _name = new char[std::strlen(arg)]; > std::strcpy(_name,arg); Array is one byte too small. > this->_name = (char*)malloc(strlen(arg)); > strcpy(this->_name,arg); Same bug here. |
David Brown <david.brown@hesbynett.no>: Aug 18 11:15AM +0200 On 17/08/2020 19:35, Juha Nieminen wrote: > 20 years ago. This year. > It is perfectly possible. You just have to watch for that 32 kB > limit. All sorts of programs can be written for small microcontrollers - I have used far smaller ones. And you can use C++ (with limitations and restrictions) on small microcontrollers. But you can't use C++ on a PIC18. I don't know the state of Microchip's C compiler these days - based on what I saw in the past, I'm suspicious of its quality. (There are good quality PIC18 compilers from other vendors, at serious prices.) The killer limit for the PIC18 here is the 3K ram space, not the 128K flash limit, and this is a core that does not do well with pointers - they are very inefficient. With just 3K of ram, use of dynamic memory is pretty much out of the question, which is a very painful restriction for any kind of networking. The MAC on the chip has 8K dedicated ram for packets, without which Ethernet would be nearly impossible, but with 3K general ram, basic network standards like DNS, DHCP and TCP/IP are almost out of the question - they are possible, but only with crippling restrictions. |
Juha Nieminen <nospam@thanks.invalid>: Aug 18 10:03AM > 3K general ram, basic network standards like DNS, DHCP and TCP/IP are > almost out of the question - they are possible, but only with crippling > restrictions. Aren't dedicated chips (of the likes of KSZ9897) used to handle ethernet in such restricted embedded systems? |
David Brown <david.brown@hesbynett.no>: Aug 18 01:22PM +0200 On 18/08/2020 12:03, Juha Nieminen wrote: >> restrictions. > Aren't dedicated chips (of the likes of KSZ9897) used to handle ethernet > in such restricted embedded systems? That's a switch chip - you use it for making an Ethernet switch, not for communicating over Ethernet. (If you open up a simple off-the-shelf 4-port Ethernet switch, it will have a chip like that in the middle, and no microcontroller.) There are chips, or modules, that are made for adding Ethernet to existing small microcontroller designs. There the chip (or module) has the software to implement TCP/IP, and usually at least some of HTTP, HTTPS, DNS, SMTP, etc., as servers and/or clients. The chip communicates with the microcontroller by UART or SPI. The idea is that as much as possible is handled on that chip, and the microcontroller is only responsible for executing commands or returning data. You use such devices when you have an existing design that you want to upgrade to use Ethernet (or Wifi) but don't want to re-do the design using a more powerful and modern device. For a while, many years ago (maybe 15-20 years), there was a demand for small microcontrollers with Ethernet support. At that time, 32-bit devices and reasonable ram sizes were big, expensive, and hard to use - so 8-bit and 16-bit devices were much more popular. There was a market for such things with very limited and simple network communication - things like embedded web servers where the page would fit within one Ethernet frame so that you didn't need fragmentation, and where a TCP/IP retry was handled by the application re-creating the dynamic page with the old data because you didn't have enough ram do buffer anything. The networking code was highly specialised and tied tightly to the application code, because there were no resources for generalisation. The PIC18 discussed here is from that era. These days, you get 32-bit microcontrollers with plenty of flash and ram and a 100 Mbit MAC for a fraction of the price of the PIC18. There are off-the-shelf network stacks and plenty of examples around, and you have enough power to have a web server, and a Modbus server, and telnet servers, and SSL encryption and whatever else you may want for a useful device that is suitable for today's networks. |
boltar@nowhere.co.uk: Aug 18 05:49PM On Tue, 18 Aug 2020 11:15:37 +0200 >3K general ram, basic network standards like DNS, DHCP and TCP/IP are >almost out of the question - they are possible, but only with crippling >restrictions. Why would you even bother? If you need proper networking use a PIC with a bigger memory. They cost buttons and there are plenty to choose from. |
Sam <sam@email-scan.com>: Aug 17 07:30PM -0400 Juha Nieminen writes: > >> tantrums and insults like a petulant child? > > Yes, I do: they suck. > So a petulant child you are. I will thus be treating you as one. You are operating under an impression that I value your opinions very highly. Not sure where you got that impression from. |
red floyd <no.spam.here@its.invalid>: Aug 17 08:22PM -0700 On 8/16/2020 11:24 PM, David Brown wrote: > "invent" coroutines. Amongst others, they are discussed by Knuth - who > describes subroutines (i.e., "functions" in C and C++) as just being > special cases of coroutines. In addition, Coroutines (called Tasks) were actually part of Modula-2 language specification (as of 1983). |
red floyd <no.spam.here@its.invalid>: Aug 17 08:22PM -0700 On 8/17/2020 8:22 PM, red floyd wrote: >> special cases of coroutines. > In addition, Coroutines (called Tasks) were actually part of Modula-2 > language specification (as of 1983). Pardon me, they were called "Processes" |
"Alf P. Steinbach" <alf.p.steinbach+usenet@gmail.com>: Aug 18 07:53AM +0200 On 18.08.2020 05:22, red floyd wrote: >> In addition, Coroutines (called Tasks) were actually part of Modula-2 >> language specification (as of 1983). > Pardon me, they were called "Processes" They were called coroutines. Possibly you're thinking of Modula-2's asynchronous coroutines. There was a book about development in Modula-2 I had (still have in a box somewhere) called "Modula-2: A Software Development Approach" by Ford & Wiener, which presented an abstraction over coroutines and asynchronous interrupt-driven coroutines. A kind of threading abstraction where at the higher levels you code up things without knowing whether it will execute all in one single thread or in several, whatever. It's an old idea, e.g. one Algol compiler in the early 1960's was based on coroutines where it could execute either as one single process, or as sequentially invoked multiple phases. I mentioned the Modula-2 abstraction idea to Bjarne when he presented `std::future` and friends in Oslo mid 2000's. However, he appeared just perplexed at my enthusiasm for the idea. At that time coroutines were not yet on the table for C++, so maybe discussing an abstraction over something he presented + something not yet envisioned, was premature... - Alf |
You received this digest because you're subscribed to updates for this group. You can change your settings on the group membership page. To unsubscribe from this group and stop receiving emails from it send an email to comp.lang.c+++unsubscribe@googlegroups.com. |
No comments:
Post a Comment