C ansi format macro global char[] overwrite - c

I have a coding assignment that must be done in C. I got sidetracked and decided to try to make a format macro i can use for styling text with ansi codes. My code is as follows:
<format.h>
#include <stdarg.h>
#include <string.h>
/**
* global buffer to store format arguments for prints
*/
char format_buffer[128];
/**
* Count the number of char* in variadic arguments, auxiliary for FMT macro
*/
#define FARGS(...) (sizeof((char*[]){__VA_ARGS__})/sizeof(char*))
/**
* Automatically fills in parameters buffer and argc for the format function. Buffer is a global variable, therefore it is never out of scope.
*/
#define FMT(...) format(format_buffer, FARGS(__VA_ARGS__), __VA_ARGS__)
#define A_START "\x1B["
#define A_END "m"
/**
* creates a format string based on input parameters.
* #param argc the number of ansi flags to use
* #param buffer the buffer to write the format string into. A size of 64 should be enough for most reasonable uses
* #param ... the ansi flags to use. Examples include RED, BLUE, DASH, etc
* #return the same pointer buffer, for ease of use in printf functions
*/
char* format(char* buffer, int argc, ...);
// ansi macros for formatting, all of the style ";NN"
<format.c>
#include "format.h"
char format_buffer[128] = {};
char* format(char* buffer, int argc, ...){
buffer[0] = '\0';
va_list argv;
int i;
// initialize argv to get data from the variable arguments
va_start(argv, argc);
strcat(buffer, A_START);
/* access all the arguments assigned to valist */
for (i = 0; i < argc; i++) {
strcat(buffer, va_arg(argv, char*));
}
strcat(buffer, A_END);
/* clean memory reserved for valist */
va_end(argv);
return buffer;
}
Using that, I can call the macro as follows, which is what I want:
printf("%sHello!\n", FMT(RED, BOLD)) //prints <Hello!\n> in red and bold
The problem
The problem I have is when I try to use multiple calls in the same print statement:
printf("%sHello, %sWorld!\n", FMT(RED, BOLD), FMT(YELLOW)) //prints <Hello, World!\n> in yellow
I'm positive it's not working as expected because FMT(...) always returns the same global char*, but I don't know how could i change it so that:
I can call the format macro as shown above: printf("%sHello!\n", FMT(RED, BOLD)).
I can use multiple FMT calls in the same print statement, as in printf("%sHello, %sWorld!\n", FMT(RED, BOLD), FMT(YELLOW)) should print <Hello, > in red and bold and <World!\n> in yellow.
As a final note, I'd rather code the solution instead of using a library or header that already has an implementation of this.
I first tried creating a new char[] inside the format function, but it would be stored in the stack so I assume that's a much worse outcome.

Simply change the macro to:
#define FMT(...) format((char[128]){}, FARGS(__VA_ARGS__), __VA_ARGS__)
https://godbolt.org/z/KvWWcYfPb

After reading both comments from Jonathan Leffler and the answer from 0___________ I've changed the files to this:
<format.h>
#include <stdarg.h>
#include <string.h>
/**
* Count the number of char* in variadic arguments, auxiliary for FMT macro
*/
#define FARGS(...) (sizeof((char*[]){__VA_ARGS__})/sizeof(char*))
#define FMT_BUF_SIZE 64
/**
* Automatically fills in parameters buffer and argc for the format function.
*/
#define FMT(...) format((char[FMT_BUF_SIZE]){""}, FARGS(__VA_ARGS__), __VA_ARGS__)
#define FMT_START "\x1B["
#define FMT_END "m"
// ansi flags for text styling, there's a lot of them
//black
#define BLACK ";30"
//red
#define RED ";31"
//green
#define GREEN ";32"
//yellow
#define YELLOW ";33"
//blue
#define BLUE ";34"
//magenta
#define MAGENTA ";35"
//cyan
#define CYAN ";36"
//bright gray
#define GRAY_B ";37"
//black background
#define BLACK_BG ";40"
//red background
#define RED_BG ";41"
//green background
#define GREEN_BG ";42"
//yellow background
#define YELLOW_BG ";43"
//blue background
#define BLUE_BG ";44"
//magenta background
#define MAGENTA_BG ";45"
//cyan background
#define CYAN_BG ";46"
//bright gray background
#define GRAY_BG_B ";47"
//gray
#define GRAY ";90"
//bright red
#define RED_B ";91"
//bright green
#define GREEN_B ";92"
//bright yellow
#define YELLOW_B ";93"
//bright blue
#define BLUE_B ";94"
//bright magenta
#define MAGENTA_B ";95"
//bright cyan
#define CYAN_B ";96"
//white
#define WHITE ";97"
//gray background
#define GRAY_BG ";100"
//bright red background
#define RED_BG_B ";101"
//bright green background
#define GREEN_BG_B ";102"
//bright yellow background
#define YELLOW_BG_B ";103"
//bright blue background
#define BLUE_BG_B ";104"
//bright magenta background
#define MAGENTA_BG_B ";105"
//bright cyan background
#define CYAN_BG_B ";106"
//white background
#define WHITE_BG ";107"
#define DASH ";9"
#define BOLD ";1"
#define ITALICS ";3"
#define STRIKETHROUGH ";9"
// I can't explain this one
#define REVERSE ";7"
#define CLEAR ";0"
// underline light
#define UNDERLINE_L ";4"
// underline heavy
#define UNDERLINE_H ";21"
/**
* creates a format string based on input parameters.
* #param argc the number of ansi flags to use
* #param buffer the buffer to write the format string into. A size of 64 should be enough for most reasonable uses
* #param ... the ansi flags to use. Examples include RED, BLUE, DASH, etc
* #return the same pointer buffer, for ease of use in printf functions
*/
char* format(char* buffer, int argc, ...);
<format.c>
#include "format.h"
char* format(char* buffer, int argc, ...){
buffer[0] = '\0';
va_list argv;
int i;
// calculate available memory in buffer available for ANSI codes
int buffer_len = FMT_BUF_SIZE - strlen(FMT_START) - strlen(FMT_END) - 1;
// initialize argv to get data from the variable arguments
va_start(argv, argc);
strcat(buffer, FMT_START);
/* access all the arguments assigned to valist */
for (i = 0; i < argc; i++) {
// retrieve ansi code and substract length to available length to prevent overflows
char* code = va_arg(argv, char*);
buffer_len -= strlen(code);
// concatenate ansi code if there's space available in the buffer, else break out of loop
if(buffer_len >= 0) strcat(buffer, code);
else break;
}
strcat(buffer, FMT_END);
/* clean memory reserved for valist */
va_end(argv);
return buffer;
}
The macro works as intended, and the format function will now stop reading arguments early if the format buffer capacity is exceeded (the buffer must at least be of length 4, and i believe the longest individual ansi code is 4 characters long).

Related

Result of bitwise OR operator in c

I have a question about bitwise OR | operator from c to rust.
I have this lot of define to convert from c to rust, and i’m not sure of the flag SEFLG_ASTROMETRIC.
#define SEFLG_JPLEPH 1 /* use JPL ephemeris */
#define SEFLG_SWIEPH 2 /* use SWISSEPH ephemeris */
#define SEFLG_MOSEPH 4 /* use Moshier ephemeris */
#define SEFLG_HELCTR 8 /* heliocentric position */
#define SEFLG_TRUEPOS 16 /* true/geometric position, not apparent position */
#define SEFLG_J2000 32 /* no precession, i.e. give J2000 equinox */
#define SEFLG_NONUT 64 /* no nutation, i.e. mean equinox of date */
#define SEFLG_SPEED3 128 /* speed from 3 positions (do not use it,
* SEFLG_SPEED is faster and more precise.) */
#define SEFLG_SPEED 256 /* high precision speed */
#define SEFLG_NOGDEFL 512 /* turn off gravitational deflection */
#define SEFLG_NOABERR 1024 /* turn off 'annual' aberration of light */
#define SEFLG_ASTROMETRIC (SEFLG_NOABERR|SEFLG_NOGDEFL) /* astrometric position,
* i.e. with light-time, but without aberration and
* light deflection */
#define SEFLG_EQUATORIAL (2*1024) /* equatorial positions are wanted */
#define SEFLG_XYZ (4*1024) /* cartesian, not polar, coordinates */
#define SEFLG_RADIANS (8*1024) /* coordinates in radians, not degrees */
#define SEFLG_BARYCTR (16*1024) /* barycentric position */
#define SEFLG_TOPOCTR (32*1024) /* topocentric position */
#define SEFLG_ORBEL_AA SEFLG_TOPOCTR /* used for Astronomical Almanac mode in
* calculation of Kepler elipses */
#define SEFLG_SIDEREAL (64*1024) /* sidereal position */
#define SEFLG_ICRS (128*1024) /* ICRS (DE406 reference frame) */
#define SEFLG_DPSIDEPS_1980 (256*1024) /* reproduce JPL Horizons
* 1962 - today to 0.002 arcsec. */
#define SEFLG_JPLHOR SEFLG_DPSIDEPS_1980
#define SEFLG_JPLHOR_APPROX (512*1024) /* approximate JPL Horizons 1962 - today */
1024 | 512
I tried with the calculator of my mac 1024 OR 512 and the result is 1536. Is this result correct ?
Yes, the result is correct: in fact, OR-ing 1024 and 512 yields the same number as adding them.
This is a convenient "shortcut" for OR-ing powers of two: since their digits never occupy the same position, addition is the same as "OR".
The same principle is in play here as with adding decimal numbers with zeros in all but a single position, when non-zero positions do not overlap: adding, say, 6000+900+30+7 is the same as writing down the individual digits into 6937, because place values do not interact with each other. OR-ing distinct powers of two in binary is the same as writing down all ones in positions designated by numbers being OR-ed.
Note that the expression (SEFLG_NOABERR|SEFLG_NOGDEFL) is evaluated at compile time, so replacing it with 1536 is not going to yield any run-time improvement.
In a word - yes. Note that 1024 and 512 don't have "1" bits in the same positions, so the result of bitwise-oring them is the same as adding them.
Yes it is correct.
512 =01000000000
1024=10000000000
1536=11000000000
OR-Calculation:
10000000000
|01000000000
=11000000000

Unable to retrieve data area value using QXXRTVDA and brace-initialized values

I was trying to do simple data area value retrieval using C source from here:
https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_71/rtref/qxxrtvd.htm
And... the code failed to do certain retrieval.
Here is the code:
typedef struct _DTAA_NAME_T {
char dtaa_name[10]; /* name of data area */
char dtaa_lib[10]; /* library that contains data area */
}_DTAA_NAME_T;
and
#include <stdio.h>
#include <xxdtaa.h>
#define DATA_AREA_LENGTH 30
#define START 6
#define LENGTH 7
int main(void)
{
char uda_area[DATA_AREA_LENGTH];
/* Retrieve data from user-defined data area currently in MYLIB */
_DTAA_NAME_T dtaname = {"USRDDA ", "MYLIB "};
/* Use the function to retrieve some data into uda_area. */
QXXRTVDA(dtaname,START,LENGTH,uda_area);
/* Print the contents of the retrieved subset. */
printf("uda_area contains %7.7s\n",uda_area);
}
There are several problems in this brace-intialized values:
_DTAA_NAME_T dtaname = {"USRDDA ", "MYLIB "};
Values can not get initialized with 10 characters value - only 9 chars + null byte.
Initializing values with 10 characters will lead to compilation error.
Initializing it with anything less than 10 characters will lead to null byte appending thus the QXXRTVDA will not find any values as soon as there's no such library with null byte ending.
The workaround I've found is to avoid brace-initialization and initialize values with direct bytes copying:
#include <stdio.h>
#include <xxdtaa.h>
#include <QSYSINC/MIH/CPYBLA>
#define DATA_AREA_LENGTH 30
#define START 6
#define LENGTH 7
int main(void)
{
char uda_area[DATA_AREA_LENGTH];
_DTAA_NAME_T dtaara;
memset( &dtaara, ' ', sizeof(dtaara));
cpybla( dtaara.dtaa_name, "USRDDA", 6);
cpybla( dtaara.dtaa_lib, "MYLIB", 5);
/* Use the function to retrieve some data into uda_area. */
QXXRTVDA(dtaname,START,LENGTH,uda_area);
/* Print the contents of the retrieved subset. */
printf("uda_area contains %7.7s\n",uda_area);
}
Hope this helps somebody not to waste time on such a basic thing.
The null-termation byte is only used to initialize the array if there is room for it.
#include <xxdtaa.h>
main()
{
_DTAA_NAME_T dtaara = {"ABCDEFGHIJ", "KLMNOPQRST"};
return;
}
In debug:
EVAL dtaara:x 20
00000 C1C2C3C4 C5C6C7C8 C9D1D2D3 D4D5D6D7 - ABCDEFGHIJKLMNOP
00010 D8D9E2E3 ........ ........ ........ - QRST............

Understanding a function definition in a header file

I am porting a software written to a specific microcontroller to another microcontroller but I have a problem in C language.
I would like to draw attention to the below mentioned functions defined inside hal_spi_rf_trxeb.c file. Although I searched within the file I was not able to find the full function description for the following functions.
TRXEM_SPI_WAIT_DONE()
TRXEM_SPI_RX()
TRXEM_SPI_WAIT_DONE()
TRXEM_SPI_WAIT_TX_DONE()
TRXEM_SPI_RX()
TRXEM_SPI_WAIT_MISO_LOW(x)
After a bit more searching I figured that these functions exist in the header file itself. More specifically inhal_spi_rf_trxeb.h file.
/******************************************************************************
* #fn trx16BitRegAccess
*
* #brief This function performs a read or write in the extended adress
* space of CC112X.
*
* input parameters
*
* #param accessType - Specifies if this is a read or write and if it's
* a single or burst access. Bitmask made up of
* RADIO_BURST_ACCESS/RADIO_SINGLE_ACCESS/
* RADIO_WRITE_ACCESS/RADIO_READ_ACCESS.
* #param extAddr - Extended register space address = 0x2F.
* #param regAddr - Register address in the extended address space.
* #param *pData - Pointer to data array for communication
* #param len - Length of bytes to be read/written from/to radio
*
* output parameters
*
* #return rfStatus_t
*/
rfStatus_t trx16BitRegAccess(uint8 accessType, uint8 extAddr, uint8 regAddr, uint8 *pData, uint8 len)
{
uint8 readValue;
<span style="background-color:#ff0000;">TRXEM_SPI_BEGIN();</span>
while(TRXEM_PORT_IN & TRXEM_SPI_MISO_PIN);
/* send extended address byte with access type bits set */
<span style="background-color:#ff0000;"> TRXEM_SPI_TX</span>(accessType|extAddr);
TRXEM_SPI_WAIT_DONE();
/* Storing chip status */
readValue = TRXEM_SPI_RX();
TRXEM_SPI_TX(regAddr);
TRXEM_SPI_WAIT_DONE();
/* Communicate len number of bytes */
trxReadWriteBurstSingle(accessType|extAddr,pData,len);
hal_spi_rf_trxeb.h claims to have the function I was searching in the form of a macro. Those macros are shown below.
/******************************************************************************
* MACROS
*/
/* Macros for Tranceivers(TRX) */
#define TRXEM_SPI_BEGIN() st( TRXEM_PORT_OUT &= ~TRXEM_SPI_SC_N_PIN; NOP(); )
#define TRXEM_SPI_TX(x) st( UCB0IFG &= ~UCRXIFG; UCB0TXBUF= (x); )
#define TRXEM_SPI_WAIT_DONE() st( while(!(UCB0IFG & UCRXIFG)); )
#define TRXEM_SPI_WAIT_TX_DONE() st( while(!(UCB0IFG & UCTXIFG)); )
#define TRXEM_SPI_RX() UCB0RXBUF
#define TRXEM_SPI_WAIT_MISO_LOW(x) st( uint8 count = 200; \
while(TRXEM_PORT_IN & TRXEM_SPI_MISO_PIN) \
{ \
__delay_cycles(5000); \
count--; \
if (count == 0) break; \
} \
if(count>0) (x) = 1; \
else (x) = 0; )
Questions
I do not understand how these macros work.
Could someone please tell me how a function has been defined in the header file?
How can a function be defined in the header file itself?
What does the function st() do?
Should you require, the whole project can be downloaded by this link.
For more information you may view the linked header and source (.h and .c) files.
As you have already said, those are not functions but rather macros. You can read about macros in the gcc manual.
A macro is a fragment of code which has been given a name. Whenever
the name is used, it is replaced by the contents of the macro.
That's the summary. There's a lot more detail to it than that. But for starters you can think of macros as code replacement rules. Macros are expanded by the preprocessor before the compiler does it's job.
A simple example:
#define MY_PRINT_MACRO(string1, string2) printf("%s %s\n", string1, string2)
MY_PRINT_MACRO("hello", "world");
The preprocessor will change the second line to be:
printf("%s %s\n", "hello", "world");
And that is exactly what the compiler will see (it does not see MY_PRINT_MACRO at all).
These are not functions but macros. Macros are basically being replaced by the code they represent before compilation (preprocessing).
Macros can be defined and redefined over header files.
st() function is not written in the context so not sure what it does.

How is the audio played in this code (C/SDL)

I think this is meant to be a 4k style of visualization program but written in C.
So it draws some spheres and plays some music, I am not trying to understand the graphics, that is generally maths. It also uses SDL. I am trying to understand where the music is coming from, there isn't any apparent function for the audio.
The music is initialized SDL_AudioSpec as, oa; and some parameters are set but where does the tune actually come from, is this a build in sample from SDL.
/* "Shadow Sun" */
/* tonic - http://jet.ro */
#include "SDL.h"
#include "SDL_opengl.h"
#include <math.h>
#include <stdlib.h>
#define S1(a,b,c) #a".."#b".."#c"."
#define X2(a) a a
#define X4(a) a a a a
#define S14(a,b,c) X4(S1(a,b,c))
#define S2(a) #a"."#a"....."
#define S24(a) X4 ( S2 ( a ) )
#define S4(a) #a #a #a #a
#define S3(a,b, c) S1(a,b,c)S4(..)
#define SE X4( X4( S4 (..)))
#define P(x) glDisable(GL_##x);
#define EF_(x) F[C][1638##x+M
#define B float
char l[]=SE SE X2(S3(J,H,M)S3(J,H,E)S3(F,E,A)S3(F,E,J)S3(H,E
,O)S3( H,E,C)S3 (H,F,x) S3(H,F,L)) ;
char b[] = SE X2(S4(j.jv.j.v)S4
(f.fr.f.r)S4(e.eq.e.q)S4(h.ht.h.t))SE;
char*r [3] ={S14(v,v,q)S14(r,r,m)
S14(q,q,l)S14(t,t,o),S24(J)S24(F)S24(E)S24(H),S24(M)S24(J)S24(H)S24(L) };
Uint32 S;GLUquadric*Q;
int D,R,U,L,p=0;B T,F[5][0100000]={{0},{0}};void G(
void*u,Uint8*_,int L){while(L>0){int M=p&037777,s=p>>12,H=(s%R)[l],J=(s
#define EF(C,l,s,ms) ((s*ms+EF_(4)-l]*(1-ms))*0.5f+EF_(3)]*0.5f)
%U)[b],C=0,a=0,n;
B v,q,m,E=(0x2000-(p&017777))/8192e0f;
#define FQ(s) (B)sin((0.12*pow(2,n/12.0))*p*s)
#define IZ(v,c) if(v c)v*=v;else v=0;
#define BF F[C][M]=F[C][M+16384]=q;v+=q;++C
#define IP(n,x,y,z,c) if(n!='.'){x}q=EF(C,y,q,z);c;BF;
#define IS(s,c) if(q c s 0.75f)q=s 0.75f;
#define NQ(N,a,m) n=N-(N>='a'?'a'+24:'A')+a;q=FQ(m);
v=q=0;IP(H,NQ(H,12,1)m=FQ(1.01f);IZ(q,>
0.6)IZ(m,>0.6)q+=m;q*=E*0.5f+0.5f;,
12288,0.2f,;)for(;a<3;++a){char
sn=(s%D)[a[r]];q=0;IP (sn,NQ(
sn,12,0.25f)IZ(q,<0.3),12288,
0.2f,;)}q=0;IP(J,NQ(J,0,0.125f)
X4(q*=q;)q=(B)fmod(q*20,4)*4;
,6144,0.25f,IS(-,<)IS(+,>)q*=
E)v*=0.6f;*(Sint16*)_=(Sint16
)(v*32767);_+=2;++p;L-=2;}}
#define N(x) glEnable(GL_##x);
void I(int a){B b[]={a?0.f:1,a?0:4.f,-2.f,0},c[]={0,
-2,0,0},d[]={.2f,0.f,0.f ,1},e[]={a?.5f:.8f,a?.75f
:.3f,a?1:.2f,1},f[]={0,.2f,.3f,1},g[]={0,0,0,1,1,1,1},*h=&g[3];
#define GF(l,x,v) glLightfv(GL_LIGHT##l,GL_##x,v);
GF(0,POSITION,b)GF(0,AMBIENT,d)GF(0,DIFFUSE,e)GF(0,SPECULAR,h)if(a)P(LIGHT1
)else N(LIGHT1)GF(1,POSITION,c)GF(1,AMBIENT,f)GF(1,DIFFUSE,f)GF(1,SPECULAR,g)
#define GM(t,x,v) glMaterial##t(GL_FRONT,GL_##x,v);
GM(fv,AMBIENT,g)GM(fv,DIFFUSE,h)GM(fv,SPECULAR,h)GM(
fv,EMISSION,g) GM (f,SHININESS,60) glColorMaterial (
GL_FRONT,GL_DIFFUSE);N(COLOR_MATERIAL)}
void K(int b,
B c,int d){ int a; B e=c*c,g=1e0f/e;glColor3f(1,1,1);
#define BC(v,f,p,q) B v=(B)f(ta*(sin(T*p*g)*5e-2+q*c)*g)*c
for(a=0;a<b;++a){B ta=T*1e-4f+a*c;BC(x,cos,2.3e-4,0.0867);
BC(y,sin,2e-4,0.2735);
BC(z,cos,1.5e-4,0.1243);
glPushMatrix();
glTranslatef(x,y,z);
glRotatef(50,1,0,0);
glRotatef (T*5e-2f+a*20,0,1,0);
glCallList(d);
glPopMatrix();}}
int _tmain(int c,char **a)
{ SDL_Event e;
SDL_AudioSpec as, oa;if (
SDL_Init(48)<0)
exit(1);
atexit(SDL_Quit);
SDL_SetVideoMode(640,480,32,2);
as.freq=053042;
as.format=AUDIO_S16;
as.channels=1;
as.samples=4096;
as.callback=G;
D=strlen(r[0]);
R=strlen(l);
U=strlen(b);
if(
SDL_OpenAudio(&as,&oa) <0)
exit(2);
SDL_PauseAudio(0);
Q=gluNewQuadric();
L=glGenLists(2);
#define NL_ GL_COMPILE);gluSphere(Q,0.1,40
#define NL(a,d) glNewList(L+a,NL_/d,20/d);
glEndList();
NL(0,1)NL(1,5)N(LIGHTING)N(LIGHT0)N(CULL_FACE)
glShadeModel(GL_SMOOTH);
N(BLEND)glBlendFunc(
GL_ONE,GL_ONE);
S=SDL_GetTicks();
T=0;
while(!SDL_PollEvent(&e)||((e.type!=2||e.key.keysym.sym!=27)&& e.type!=12))
{
B d,f;
Uint32 g;
f=T;
g=SDL_GetTicks()-S;T=T*0.75f+g*2.5e-1f;glClearColor(0,0,0,1);
glClear(16640);
glViewport(0,0,640,480);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45,4./3,0.5,100);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
d=T*1e-4f;
gluLookAt(cos(d)*3,0,sin(d)*3,0, 0, 0, 0,1, 0);
I(0);
K(0144, 1, L);
P(COLOR_MATERIAL)I(1);K(100+0144+0x64+0620+0454,15,L+1);
P(COLOR_MATERIAL)
SDL_GL_SwapBuffers();
/* $Revision: 1.1 $*/
SDL_Delay(~0&1);
}
SDL_CloseAudio();
return 0;
}
This code is not meant to be instructive! :)
Look at the macros and you see that there are several tokenizing macros. These macros combine tokens into new tokens before the compiler sees them and are a simple and effective method for code obfuscation in C and can make the source smaller too. You will have to sift deeply through those macro expansions until you find what is happening.
The code will either call SDL_MixAudio() or directly modify the audio stream buffer in a callback. The callback pointer is stored in the SDL_AudioSpec struct passed to SDL_OpenAudio() or SDL_OpenAudioDevice(). The second parameter of the callback is the writable audio stream.
Good luck!

Is there a way to dump a C struct?

I've written a program to probe the limits of a system's C time.h functions and dump them out in JSON. Then other things which depend on those functions can know their limits.
# system time.h limits, as JSON
{
"gmtime": {
"max": 2147483647,
"min": -2147483648
},
"localtime": {
"max": 2147483647,
"min": -2147483648
},
"mktime": {
"max": {
"tm_sec": 7,
"tm_min": 14,
"tm_hour": 19,
"tm_mday": 18,
"tm_mon": 0,
"tm_year": 138,
"tm_wday": 1,
"tm_yday": 17,
"tm_isdst": 0
},
"min": {
"tm_sec": 52,
"tm_min": 45,
"tm_hour": 12,
"tm_mday": 13,
"tm_mon": 11,
"tm_year": 1,
"tm_wday": 5,
"tm_yday": 346,
"tm_isdst": 0
}
}
}
gmtime() and localtime() are simple enough, they just take numbers, but mktime() takes a tm struct. I wrote a custom function to turn a tm struct into a JSON hash.
/* Dump a tm struct as a json fragment */
char * tm_as_json(const struct tm* date) {
char *date_json = malloc(sizeof(char) * 512);
#ifdef HAS_TM_TM_ZONE
char zone_json[32];
#endif
#ifdef HAS_TM_TM_GMTOFF
char gmtoff_json[32];
#endif
sprintf(date_json,
"\"tm_sec\": %d, \"tm_min\": %d, \"tm_hour\": %d, \"tm_mday\": %d, \"tm_mon\": %d, \"tm_year\": %d, \"tm_wday\": %d, \"tm_yday\": %d, \"tm_isdst\": %d",
date->tm_sec, date->tm_min, date->tm_hour, date->tm_mday,
date->tm_mon, date->tm_year, date->tm_wday, date->tm_yday, date->tm_isdst
);
#ifdef HAS_TM_TM_ZONE
sprintf(&zone_json, ", \"tm_zone\": %s", date->tm_zone);
strcat(date_json, zone_json);
#endif
#ifdef HAS_TM_TM_GMTOFF
sprintf(&gmtoff_json", \"tm_gmtoff\": %ld", date->tm_gmtoff);
strcat(date_json, gmtoff_json);
#endif
return date_json;
}
Is there a way to do this generically, for any given struct?
Note: C, not C++.
Not in C—at least in general. But if the C module is compiled with debug symbols, and the object module is available, you could parse that and discover everything about the structure. I bet there's a library for your system to assist with that.
Having come across the same issue, i wrote one myself. https://github.com/jamie-pate/jstruct . It's written to allow annotating existing c structures, then generate meta-data information based on the annotations. The library reads the metadata to import/export c structures to json strings and back. A python script takes care of parsing the annotated header and generating new headers and metadata initializers. The jstruct library uses https://github.com/json-c/json-c internally.
I have also noticed https://github.com/marel-keytech... but that was after writing the entire thing. (and info on that project's page is sparse)
There's no support yet for annotating a single struct from an existing system lib but you could wrap the tm struct in an annotated custom struct. (I think?)
Feel free to add features requests or even pull requests if you have ideas that would make the library more useful to you. One idea I had would be to add a way to embed that kind of read only struct inside an annotated wrapper with a #inline annotation: eg (currently unsupported but could be added)
//#json
struct my_time {
//#inline
struct tm tm;
}
Code:
struct my_time t;
mktime(&t.tm);
struct json_object *result = jstruct_export(t, my_time);
In the mean time you could do the same thing without #inline (since it hasn't been written yet) and just extract the tm property by hand with json_object_to_json_string(json_object_object_get(result, "tm"))
This won't quite give you what you're asking for, but it might help a little:
#define NAME_AND_INT(buf, obj, param) \
sprintf((buf), "\"%s\": %d, ", #param, (obj)->(param))
You could then iterate, e.g. something like (note: not tested; consider this pseudo-code):
char * tm_as_json(const struct tm* date) {
/* ... */
char buf[BUFSIZ]; /* or, use your date_json */
pos = buf; /* I note you use the equivalent of &buf -- that works too */
/* (not sure which is "better", but I've always left the & off
* things like that -- buf is essentially a pointer, it's just
* been allocated in a different way. At least that's how I
* think of it. */
pos += NAME_AND_INT(pos, date, tm_sec);
pos += NAME_AND_INT(pos, date, tm_min);
/* ... more like this ... */
/* strip trailing ", " (comma-space): */
pos-=2;
*pos = '\0';
/* ... */
}
You could similarly define NAME_AND_STRING, NAME_AND_LONG, etc. (for tm_zone and tm_gmtoff) as needed.
Again, it's not a generic solution, but it at least gets you a little closer, maybe.
Disclaimer: I'm the owner of the project https://github.com/tamask1s/zax-parser
With the help of the library, you can convert a C struct to JSON if you provide some information on your struct members which needs to be converted. There will be no need to include generated code in your project, but you will need a c++11 compiler in order to use it.
The lib is quite immature because I have implemented only the features I needed, but you may extend it, or you may use it as inspiration.
Example:
#define some_json_properties JSON_PROPERTY(x), JSON_PROPERTY(s)
struct some_class
{
int x = 9;
std::string s = "something";
ZAX_JSON_SERIALIZABLE(some_class, some_json_properties)
};
std::string some_json = some_obj;
---some_json's value:---
{"x":9, "s":"something"}
Nesting of objects is also possible, please check this example: https://tamask1s.github.io/zax-parser/index.html#Parsing_of_structures_with_fields_of_serializable_structures
Tom Christiansen once wrote pstruct/h2ph which is in perl CORE to parse .stabs info from the used compiler, and create readable info for all data structures.
C structs into JSON is trivial based on h2ph.
http://perl5.git.perl.org/perl.git/blob/HEAD:/utils/h2ph.PL
This macro does not do exactly what you want (generate JSON dump of C data), but I think it shows some possibility. You can dump content of any C data with a "p(...);" call.
I used gdb as external helper to make this work, but it is possible to implement one with libbfd. In that case, you can fully control your output - like generating JSON compatible output.
#ifndef PP_H
#define PP_H
/*
* Helper function (macro) for people who loves printf-debugging.
* This dumps content of any C data/structure/expression without prior
* knowledge of actual format. Works just like "p" or "pp" in Ruby.
*
* Usage:
* p(anyexpr);
*
* NOTE:
* - Program should be compiled with "-g" and preferrably, with "-O0".
*
* FIXME:
* - Would be better if this doesn't depend on external debugger to run.
* - Needs improvement on a way prevent variable from being optimized away.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
// Counts number of actual arguments.
#define COUNT_(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
#define COUNT(...) COUNT_(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1)
// Dispatches macro call by number of actual arguments.
// Following is an example of actual macro expansion performed in case
// of 3 arguments:
//
// p(a, b, c)
// -> FUNC_N(p, COUNT(a, b, c), a, b, c)
// -> FUNC_N(p, 3, a, b, c)
// -> p_3(a, b, c)
//
// This means calling with simple "p(...)" is fine for any number of
// arguments, simulating "true" variadic macro.
#define CONCAT(name, count) name##count
#define FUNC_N(name, count, ...) CONCAT(name, count)(__VA_ARGS__)
// Forbids variable from being optimized out, so debugger can access it.
//
// FIXME:
// - Current implementation does not work with certain type of symbols
#define ENSURE(...) FUNC_N(ENSURE_, COUNT(__VA_ARGS__), __VA_ARGS__)
#define ENSURE_1(a) asm(""::"m"(a))
#define ENSURE_2(a, ...) do { ENSURE_1(a); ENSURE_1(__VA_ARGS__); } while (0)
#define ENSURE_3(a, ...) do { ENSURE_1(a); ENSURE_2(__VA_ARGS__); } while (0)
#define ENSURE_4(a, ...) do { ENSURE_1(a); ENSURE_3(__VA_ARGS__); } while (0)
#define ENSURE_5(a, ...) do { ENSURE_1(a); ENSURE_4(__VA_ARGS__); } while (0)
#define ENSURE_6(a, ...) do { ENSURE_1(a); ENSURE_5(__VA_ARGS__); } while (0)
#define ENSURE_7(a, ...) do { ENSURE_1(a); ENSURE_6(__VA_ARGS__); } while (0)
#define ENSURE_8(a, ...) do { ENSURE_1(a); ENSURE_7(__VA_ARGS__); } while (0)
// Dumps content of given symbol (uses external GDB for now)
//
// NOTE:
// - Should use libbfd instead of gdb? (but this adds complexity...)
#define PP_D(...) do { \
char *arg[] = { __VA_ARGS__, NULL }; \
char **argp = arg; \
char cmd[1024]; \
FILE *tmp = tmpfile(); \
fprintf(tmp, "attach %d\n", getpid()); \
fprintf(tmp, "frame 2\n"); \
while (*argp) \
fprintf(tmp, "p %s\n", *argp++); \
fprintf(tmp, "detach\n"); \
fflush(tmp); \
sprintf(cmd, "gdb -batch -x /proc/%d/fd/%d", \
getpid(), fileno(tmp)); \
system(cmd); \
fclose(tmp); \
} while (0)
#define PP(...) do { \
FUNC_N(PP_, COUNT(__VA_ARGS__), __VA_ARGS__); \
ENSURE(__VA_ARGS__); \
} while (0)
#define PP_1(a) do { PP_D(#a); } while (0)
#define PP_2(a,b) do { PP_D(#a,#b); } while (0)
#define PP_3(a,b,c) do { PP_D(#a,#b,#c); } while (0)
#define PP_4(a,b,c,d) do { PP_D(#a,#b,#c,#d); } while (0)
#define PP_5(a,b,c,d,e) do { PP_D(#a,#b,#c,#d,#e); } while (0)
#define PP_6(a,b,c,d,e,f) do { PP_D(#a,#b,#c,#d,#e,#f); } while (0)
#define PP_7(a,b,c,d,e,f,g) do { PP_D(#a,#b,#c,#d,#e,#f,#g); } while (0)
#define PP_8(a,b,c,d,e,f,g,h) do { PP_D(#a,#b,#c,#d,#e,#f,#g,#h); } while (0)
// Comment this out if you think this is too aggressive.
#define p PP
#endif
Indentation is lost in above paste, but you can grab the source from: https://github.com/tai/ruby-p-for-c

Resources