Typedef in C

Thread Starter

Ian0

Joined Aug 7, 2020
13,097
I now understand structures and unions (though initialising them sometimes makes refer to the text book), but typedef and -> still puzzle me.
I can see what they do, but all the examples I have seen in textbooks are so facile that I wonder why I should bother. Can someone give me an example of how typedef and -> are actually useful.
 

MrChips

Joined Oct 2, 2009
34,621
typedef can be used to define a variable that has a structure.
For example, if student has been declared and has name as part of the structure, you can reference the element,

student.name

Now, suppose that you want to pass the variable student to a function, you pass the variable by reference, i.e. as a pointer, for example, *s.
Now, when you need to access an element in the structure, you use

s->name

Hope this helps.
 

Thread Starter

Ian0

Joined Aug 7, 2020
13,097
It's less of a "how to" and more "why should I want to" question. Sort of a cost/benefit question. It's another thing to learn how to do, but what advantages does it give me?
I can define structures without typedef: I can pass student.name to the function already, why would it be better to pass it as a pointer?

I can perhaps see that -> is useful when the structure exists at a known memory address, such as peripherals, but when the pointer points to an address that is chosen almost randomly by the compiler why is it better to do it that way?
 

nsaspook

Joined Aug 27, 2009
16,249
Normally you can pass a pointer to structure to a function using the typedef to increase code readability by creating a 'handle' that's created and only manipulated by functions. Typedefs are just labels for abstraction that eliminates typing 'struct' and can bring a bit of sanity to using function pointer syntax.
https://www.iso-9899.info/wiki/Typedef_Function_Type

Here typedefs are used to create structures with the __packed attribute to eliminate gaps in the controller structure memory. I send X command and receive from the device a stream of register data the maps to EM_data1, EM_data2 or EM_version.

Command state sequence ID
C:
    typedef enum cmd_type {
        G_ID = 0,
        G_DATA1,
        G_DATA2,
        G_CONFIG, // keep sequence
        G_PASSWD, // keep sequence
        G_LIGHT,
        G_VERSION,
        G_SERIAL,
        G_LAST,
    } cmd_type;


    /*
     * maps the EM540 modbus registers to int32_t and uint16_t values
     */
    typedef __pack struct EM_data1 {
        volatile int32_t
        vl1n, vl2n, vl3n,
        vl1l2, vl2l3, vl3l1,
        al1, al2, al3,
        wl1, wl2, wl3,
        val1, val2, val3,
        varl1, varl2, varl3, // extra stuff
        vlnsys, vllsys, wsys, vasys, varsys;
        volatile int16_t
        pfl1, pfl2, pfl3, pfsys,
        phaseseq, hz;
    } EM_data1;

    typedef __pack struct EM_data2 {
        volatile int64_t
        kwhpt, kvarhpt, kwhpp, kvarhpp,
        kwhpl1, kwhpl2, kwhpl3,
        kwhnt, kvarhnt, kwhnp, kvarhnp,
        kvaht, kvahp;
        volatile int32_t
        rhm, rhmk, rhmp, rhmkp,
        hz, rhlc;
    } EM_data2;

    typedef __pack struct EM_serial {
        volatile char serial[14];
        volatile int16_t year;
    } EM_serial;

    typedef __pack struct EM_version {
        volatile int16_t firmware;
    } EM_version;
and we have a client typedef for a 'handle'
C:
    typedef struct C_data { // client state machine data
        uint8_t mcmd;
        comm_type cstate;
        cmd_type modbus_command;
        uint16_t req_length;
        int8_t trace;
        bool id_ok, passwd_ok, config_ok, data_ok, light_ok, serial_ok, version_ok, tm_ok;
        uint32_t data_count, data_prev;
        volatile M_data M;
    } C_data;

    typedef struct M_data { // ISR used, mainly for non-atomic mod problems
        uint8_t blink_lock : 1;
        uint8_t config : 1;
        uint8_t stable : 1;
        uint8_t boot_code : 1;
        uint8_t power_on : 1;
        uint8_t send_count, recv_count, pwm_volts;
        uint16_t error, crc_data, crc_calc;
        uint32_t crc_error;
        uint32_t to_error;
        uint32_t sends;
        volatile bool rx;
    } M_data;
C:
switch (client->modbus_command)
// code fragment
                case G_DATA1: // read code request
                    client->trace = T_data;
                    client->req_length = modbus_rtu_send_msg((void*) cc_buffer_tx, (const void *) modbus_em_data1, sizeof (modbus_em_data1));
                    break;
                case G_DATA2: // read code request
                    client->trace = T_data;
                    client->req_length = modbus_rtu_send_msg((void*) cc_buffer_tx, (const void *) modbus_em_data2, sizeof (modbus_em_data2));
                    break;
//
Sends MODBUS data and receives the reply in the typedef'd structure.

send state decoder
C:
switch (client->modbus_command)
//
                    case G_DATA1: // check for controller data1 codes
                        modbus_read_check(client, &client->data_ok, sizeof (em_data1), em_data_handler);
                        break;
                    case G_DATA2: // check for controller data2 codes
                        modbus_read_check(client, &client->data_ok, sizeof (em_data2), emt_data_handler);
                        break;
//
receive state decoder
C:
static bool modbus_read_check(C_data * client, bool* cstate, const uint16_t rec_length, void (* DataHandler)(void)) {   // <<< note the function pointer passed
    uint16_t c_crc, c_crc_rec;

#ifdef    MB_EM540
    client->req_length = rec_length;
    if (DBUG_R((M.recv_count >= client->req_length) && (cc_buffer[0] == MADDR) && (cc_buffer[1] == READ_HOLDING_REGISTERS))) {
        c_crc = crc16(cc_buffer, client->req_length - 2);
        c_crc_rec = crc16_receive(client);
        if (DBUG_R c_crc == c_crc_rec) {
            client->data_ok = true;
            *cstate = true;
            /*
             * move from receive buffer to data structure and munge the data into the correct local 32-bit format from MODBUS client
             */
            DataHandler();
            client->data_prev = client->data_count;
            client->data_count++;
            MM_ERROR_C;
        } else {
            MM_ERROR_C;
            *cstate = false;
            client->data_ok = false;
            log_crc_error(c_crc, c_crc_rec);
            if (client->data_ok) {
            }
        }
        client->cstate = CLEAR;
    } else {
        if (get_500hz(false) >= RDELAY) {
            DB0_SetLow();
            client->cstate = CLEAR;
            MM_ERROR_C;
            client->mcmd = G_ID;
            M.to_error++;
            M.error++;
            if (client->data_ok) {
            }
        }
    }
#endif
    return *cstate;
}
A DataHandler for the function pointer
C:
static void em_data_handler(void) {
    /*
     * move from receive buffer to data structure and munge the data into the correct local formats from MODBUS client
     */
    memcpy((void*) &em, (void*) &cc_buffer[3], sizeof (em)); // copy the comm buffer to the typedef variable em
    em.vl1l2 = mb32_swap(em.vl1l2);
    em.vl2l3 = mb32_swap(em.vl2l3);
    em.vl3l1 = mb32_swap(em.vl3l1);
    em.al1 = mb32_swap(em.al1);
    em.al2 = mb32_swap(em.al2);
    em.al3 = mb32_swap(em.al3);
    em.wl1 = mb32_swap(em.wl1);
    em.wl2 = mb32_swap(em.wl2);
    em.wl3 = mb32_swap(em.wl3);
    em.val1 = mb32_swap(em.val1);
    em.val2 = mb32_swap(em.val2);
    em.val3 = mb32_swap(em.val3);
    em.varl1 = mb32_swap(em.varl1);
    em.varl2 = mb32_swap(em.varl2);
    em.varl3 = mb32_swap(em.varl3);
    em.pfl1 = mb16_swap(em.pfl1);
    em.hz = mb16_swap(em.hz);
}
 
Last edited:

WBahn

Joined Mar 31, 2012
32,702
I now understand structures and unions (though initialising them sometimes makes refer to the text book), but typedef and -> still puzzle me.
I can see what they do, but all the examples I have seen in textbooks are so facile that I wonder why I should bother. Can someone give me an example of how typedef and -> are actually useful.
First, note that typedef and pointer indirection are two unrelated topics.

Using a typedef to define user-defined data types not only improves code readability and maintainability, but it enables abstraction and allows the compiler to do more thorough type checking.

Consider when you open a file. What type of variable is returned? It returns a pointer to a variable of type FILE, so you declare the variable you are going to use like:

FILE *fp;

FILE is defined via a typedef statement in stdio.h.

FILE is actually a struct that has quite a few members in it. You neither need to know, nor should know, what those fields are, what they are names, how they are organized, or anything else about them. In fact, different compilers are going to have very different FILE structures, and even the same compiler may change this over time. You really need it to be a black box as far as your code goes.

By using a typedef, you are insulated from all of those details. All you need to know is that there is a type named FILE that you need to use in order to talk to certain functions. Because it is a type, the compiler can also check to see whether or not you are actually doing that and issue warnings/errors accordingly.

Similarly, if you want a 16-bit unsigned integer, you would likely use the type uint16_t. That is a typedef that is defined in terms of the appropriate primitive data types for that compiler, which vary from one compiler to another. But you don't care, because you have a type that you can count on as being an unsigned 16-bit integer.

You can use typedefs to hide the details of your structures the same way that FILE does it by putting the structure definition in the .c file in which the functions that use that structure are defined and putting the typedef into the .h file along with the prototypes for any functions that you want the person using that structure to have access to. This limits them to only using those functions and to not being able to access the internals of the structure (at least not easily).

As far as pointer indirection, like square brackets for doing array dereferencing, it is really just syntactic sugar to make your programs more readable, writable, and maintainable.

If you have an array, say Fred, that has, say, 100 members, you can access it like normal:

x = Fred[42];

Or you could dispense with the array dereference operators (the square brackets) and do it like

x = *(Fred + 42);

But which is more readable? What if you forgot the parens and typed:

x = *Fred+42;

Because of operator precedence, this would be the same as

x = Fred[0] + 42;

Perfectly legal code, and a potentially hellish bug to track down.

This is why we use the square brackets -- it replaces two operations with one so that we don't have to get tripped up by the precedence between the two operations it actually represents.

It's even worse with structure dereferencing.

Say you have a structure variable named sue and it has a member named payrate that is of type double. You would assign a value to it like the following:

sue.payrate = 31.42;

But if you want to dynamically allocate these structures, you would need to be working with pointers to them, so if john was a pointer to one of these structures, the code you might try would be

*john.payrate = 31.42;

But this won't work like you expect, because the structure dereference operator has higher precedence than the pointer dereference operator, so this is trying to treat john as a structure (not a pointer to a structure) and then fetching the payrate member from that structure and treating it as a pointer. Not what you want. So you have to force the evaluation explicitly by using:

(*john).payrate = 31.42;

And you have to remember to do this EVERY TIME you access a member of a structure via a pointer to that structure.

So the structure pointer dereference operator was created to resolve this by, again, replacing two operations with a single operator.

The above is syntactically equivalent to:

john->payrate = 31.42;

Many compilers will actually make a first pass through the code and "desugar" it by removing all of the syntactic sugar so that the underlying compiler has a simpler syntax to work with.
 

Thread Starter

Ian0

Joined Aug 7, 2020
13,097
First, note that typedef and pointer indirection are two unrelated topics.
They are related only by the fact that they both puzzle(d) me!

Nice explanation of the arrow. The fact had eluded me that in an array such as john[], john is actually the address of the data; unlike int john which is the data and &john is its address.
As I don't need to allocate memory dynamically, I don't suppose the arrow is that useful to me. My data tends to be fixed, and global.
 

nsaspook

Joined Aug 27, 2009
16,249
They are related only by the fact that they both puzzle(d) me!

Nice explanation of the arrow. The fact had eluded me that in an array such as john[], john is actually the address of the data; unlike int john which is the data and &john is its address.
As I don't need to allocate memory dynamically, I don't suppose the arrow is that useful to me. My data tends to be fixed, and global.
I'm not sure what you mean by "I don't suppose the arrow is that useful to me. My data tends to be fixed, and global" as that's not a limitation to this source code abstraction.

It's very useful with static fixed allocation of memory objects as a code abstraction that adds type checking to complex data functions with OO type programming styles in C. I normally don't allocate memory dynamically in controller code. I pass a pointer to a static fixed typedef object 'handle' and use the arrow to access the data elements inside a function. For something like MODBUS each 'handle' controls a difference client device with a common function API with possibly different internal processing functionality for each device.
 

WBahn

Joined Mar 31, 2012
32,702
They are related only by the fact that they both puzzle(d) me!

Nice explanation of the arrow. The fact had eluded me that in an array such as john[], john is actually the address of the data; unlike int john which is the data and &john is its address.
As I don't need to allocate memory dynamically, I don't suppose the arrow is that useful to me. My data tends to be fixed, and global.
I think most programmers are hesitant to work with pointers to structures at first, and so they declare a lot of statically allocated structures and use the dot access operator. I know that was the case with me and most people I know. But, over time, most people come to realize the power that comes with working with pointers to structures and almost never use the dot access operator. I've gone a few steps beyond that and use structures in a way that make them very much like objects. Unless performance dictates otherwise (which, in the embedded world, is still often the case, but in most instances is not) I only let two functions directly access a given member of a structure -- the get() and the set() function for that member. No other function is allowed to directly access that member. I've gone so far as to create rather sophisticated macros that actually expand to a large portion of the foundational code to support object-like structures such that I can create a new object in just a few minutes and have all of that done for me.
 

Thread Starter

Ian0

Joined Aug 7, 2020
13,097
I think most programmers are hesitant to work with pointers to structures at first, and so they declare a lot of statically allocated structures and use the dot access operator. I know that was the case with me and most people I know. But, over time, most people come to realize the power that comes with working with pointers to structures and almost never use the dot access operator
Very true. it took a while for it to dawn on me how a structure could actually be useful! (That was a while ago) Then a union. Then I realised that it was the same as the way I had been handling data in assembler.

I have a structure which has a title as a string, a value as an int, and a suffix as a string, e.g. “Generator Power”, 2000, “W” with the structure called GeneratorPower and the value accessed as GeneratorPower.value
That is then in a union so I can also address it as an array and show the various values on a display in text, using a for loop.
I don’t yet see how accessing it as -> will make anything any more readable.
I could also define it using Typedef, but, apart from a little less typing where all the various parts are given their values, that probably isn’t worth changing unless I decide to do a major rewrite.

I also see structures, like the one I am using, which include not the string itself, but a pointer to the string.
 

Thread Starter

Ian0

Joined Aug 7, 2020
13,097
Thank you, gentlemen, for you assistance.
I tried a few examples of typedef, and whilst my life wasn't immeasurably improved, it did make my code a bit easier to read.
It all came unstuck, though, as my declarations are in a separate file and declared as extern, and the typedef doesn't seem to work with extern.
I haven't had a go with the arrow yet, because I got distracted by changing my structure from including strings, to including pointers to strings, and I managed that without your assistance.
 

WBahn

Joined Mar 31, 2012
32,702
Thank you, gentlemen, for you assistance.
I tried a few examples of typedef, and whilst my life wasn't immeasurably improved, it did make my code a bit easier to read.
It all came unstuck, though, as my declarations are in a separate file and declared as extern, and the typedef doesn't seem to work with extern.
I haven't had a go with the arrow yet, because I got distracted by changing my structure from including strings, to including pointers to strings, and I managed that without your assistance.
Where you will see your life being improved is when you make a logic mistake and the compiler's newfound ability to do type checking on your newly-defined types catches it.

The typedef should be in an include file and that include file should be included everywhere that that defined type is used.

To avoid the include file being included multiple times, which can cause problems under some circumstances, it is common practice to use a guard block like the following:

FILE: myinclude.h
Code:
#ifndef MYINCLUDE_DOT_H
#define MYINCLUDE_DOT_H

// All of the stuff in the include file

#endif
If you have some generic types that you use that aren't tied to a particular program file, then you could do something like:

FILE: mytypes.h
Code:
#ifndef MYTYPES_DOT_H
#define MYTYPES_DOT_H

typedef char * STRING;
// And so on.

#endif
You want to adopt some kind of convention so that you can easily spot defined types, such as using all uppercase for the type name, or upper-camel case, or using a '_t' suffix (or perhaps a '_mt' for "my type").

For a long time I had an include file that was a collection of lots of types and macros and useful function prototypes called 'dirtyd.h' (for dirty deeds done dirt cheap) and the last #include in every program I wrote for years included that file.
 

ApacheKid

Joined Jan 12, 2015
1,762
I now understand structures and unions (though initialising them sometimes makes refer to the text book), but typedef and -> still puzzle me.
I can see what they do, but all the examples I have seen in textbooks are so facile that I wonder why I should bother. Can someone give me an example of how typedef and -> are actually useful.
Take a close look at this code I crafted last year.

https://github.com/Steadsoft/embedded/tree/main/libraries/nrf24_package

This is a good example of typedefs can be useful.

A lot of work went into this, the overall source structure reflects much of my own experience of C to over many years in industry.

How this is all used is shown in these projects

https://github.com/Steadsoft/embedded/tree/main/NucleoF446RE/NRF24

The library design lets us write stuff like this in plain old C:

1696455167925.png

In essence we can easily craft "namespaces" like in this case nrf24_package.

Here are some of the typedefs

1696455406215.png

Those are defining pointer typedefs which allows the final code to be neat and easy to read.
 

Attachments

Thread Starter

Ian0

Joined Aug 7, 2020
13,097
Speaking of tidy programs, how do you deal with:
a) odd text strings that only get used once
b) weird and wonderful conversion factors that convert A/D readings in to Volts and Amps and suchlike?
 

nsaspook

Joined Aug 27, 2009
16,249
Speaking of tidy programs, how do you deal with:
a) odd text strings that only get used once
b) weird and wonderful conversion factors that convert A/D readings in to Volts and Amps and suchlike?
I usually put strings that are subject to programming or language changes in a header file with const char to make them easy to locate or in the code as a member of a C99 structure for the function using the text string if there is other data needed.
C:
static struct pcmcia_driver daq700_cs_driver = {
    .name        = "ni_daq_700",
    .owner        = THIS_MODULE,
    .id_table    = daq700_cs_ids,
    .probe        = daq700_cs_attach,
    .remove        = comedi_pcmcia_auto_unconfig,
};
https://git.kernel.org/pub/scm/linu...rivers/comedi/drivers/ni_daq_700.c?h=v6.6-rc4
For things like debug messages, it's normally inline with the code 'print' statement.
C:
{
// code fragment
        /* daq-gert ao */
        s = &dev->subdevices[2];
        s->private = devpriv->ao_spi;
        s->type = COMEDI_SUBD_AO;
        /* we support single-ended (ground)  */
        s->n_chan = thisboard->n_aochan;
        s->len_chanlist = thisboard->n_aochan;
        /* analog resolution depends on the DAC chip 8,10,12 bits */
        s->maxdata = (1 << thisboard->n_aochan_bits) - 1;
        s->range_table = &daqgert_ao_range;
        s->insn_write = daqgert_ao_winsn;
        s->insn_read = comedi_readback_insn_read;
        if (devpriv->smp) {
            s->subdev_flags = devpriv->ao_spi->device_spi->ao_subdev_flags;
            s->do_cmdtest = daqgert_ao_cmdtest;
            s->do_cmd = daqgert_ao_cmd;
            s->cancel = daqgert_ao_cancel;
        } else {
            s->subdev_flags = devpriv->ao_spi->device_spi->ao_subdev_flags - SDF_CMD_WRITE;
        }
        ret = comedi_alloc_subdev_readback(s);
        if (ret) {
            dev_err(dev->class_dev,
                "alloc subdevice readback failed!\n");
            return ret;
        }
    }

    /*
     * setup the timer to call my_timer_ai_callback
     */
    timer_setup(&devpriv->ai_spi->my_timer, my_timer_ai_callback, 0);
    /*        (unsigned long) dev); */
    /*
     * setup kthreads on other cores if possible
     */
    if (devpriv->smp) {
        ret = daqgert_create_thread(dev, devpriv);
        if (ret) {

            dev_err(dev->class_dev, "cpu thread creation failed\n");
            return ret;
        }
    }

    dev_info(dev->class_dev,
        "%s attached: gpio iobase 0x%lx, ioremaps 0x%lx  "
        "0x%lx, io pins 0x%x, 1MHz timer value 0x%x:0x%x\n",
        dev->driver->driver_name,
        dev->iobase,
        (long unsigned int) dev->mmio,
        (long unsigned int) devpriv->timer_1mhz,
        (uint32_t) s->io_bits,
        (uint32_t) ioread32(devpriv->timer_1mhz + 2),
        (uint32_t) ioread32(devpriv->timer_1mhz + 1));

    return 0;

daqgert_kfree_tx_exit:
    kfree(pdata->tx_buff);
daqgert_kfree_exit:
    return ret;
}
Linux syslog boot messages
[ 6.271394] spigert spi0.1: default setup: cd 1: 500000 Hz: bpw 8, mode 0x0
[ 6.279097] spigert spi0.0: default setup: cd 0: 500000 Hz: bpw 8, mode 0x0
[ 6.285096] comedi comedi0: 4 cpu(s) online for threads
[ 6.289397] comedi comedi0: Gertboard WiringPi pins setup
[ 6.293942] comedi comedi0: RPi new scheme rev a21041, serial 000000007678e2db, new rev 1
[ 6.298317] comedi comedi0: driver gpio board rev 3
[ 6.302839] comedi comedi0: Gertboard WPi pins set [0..7] to outputs
[ 6.306967] comedi comedi0: Gertboard device detection started, daqgert_conf option value 1
[ 6.311219] comedi comedi0: onboard mcp3202 detected with mcp4822, 2 channels, range code 0, device code 2, detect code 0
[ 6.318949] comedi comedi0: board setup: spi cd 0: 1000000 Hz: mode 0x3: assigned to adc device mcp3202
[ 6.327049] comedi comedi0: board setup: spi cd 1: 16000000 Hz: mode 0x3: assigned to dac device mcp4822
[ 6.335349] comedi comedi0: hunk ai transfers enabled, length: 1024
[ 6.340159] comedi comedi0: 16 bit and less device buffers set to 16 bits
[ 6.348005] comedi comedi0: daq_gert attached: gpio iobase 0x3f200000, ioremaps 0xbc060000 0xbc05e000, io pins 0x0, 1MHz timer value 0x0:0x9d6937
[ 6.348764] comedi comedi0: ai device thread start
[ 6.348899] comedi comedi0: ao device thread start
[ 6.370819] comedi comedi0: driver 'daq_gert' has successfully auto-configured 'Gertboard'.
Conversion factors can be defines in header file for things other than 0 or 1.
C:
// CTMU ADC results for cables
#define offset_meter    2752.0f
#define    one_meter    2752.0f
#define five_meter    3264.0f
#define cal_meters    4.0f
C:
    while (1) {
        // Add your application code
        if (printout) {
            adc_raw_float = (float) tdr_adc;
            printout = false;

            adc_scaled = adc_raw_float - offset_meter;
            r_m = five_meter - one_meter;
            s_m = r_m / cal_meters;
            l_m = (adc_scaled / s_m) + 1.0f;
//            snprintf(buffer, 255, "TDR %.2fM cable ", l_m);
            snprintf(buffer, 255, "ADC %.2f ", adc_raw_float);
            printf("%s", buffer);
        }
    }
I've coded systems where sets of conversion factors are each in their individual typdef'd 'factors' structure variable. This makes it easy to change whole sets of factors with one pointer variable passed to the conversion routine.
 
Last edited:

Thread Starter

Ian0

Joined Aug 7, 2020
13,097
Finally realised what makes structures with pointers more useful than ordinary ones.
Passing the structure to a function.
(Might not be the only thing)
Yes, I know it was October and now it's April. It's taken a while. . . .
 

ApacheKid

Joined Jan 12, 2015
1,762
Finally realised what makes structures with pointers more useful than ordinary ones.
Passing the structure to a function.
(Might not be the only thing)
Yes, I know it was October and now it's April. It's taken a while. . . .
Well bear in mind that this is only because the C language passes all arguments by value. That necessitates a way to pass structures by reference and so we pass a pointer (by value) to the structure.
 

nsaspook

Joined Aug 27, 2009
16,249
I'm mainly an electronics engineer, so knowing ONE high level language is enough for me!
I keep telling programmers that. C is a tool, used because it's simple and rugged, like my Estwing hammer.
1712263613648.png
It's not a religion that HW guys cling to because we were brainwashed.
 
Last edited:

ApacheKid

Joined Jan 12, 2015
1,762
I keep telling programmers that. C is a tool, used because it's simple and rugged, it's not a religion that HW guys cling to because we were brainwashed.
If your professional duties entail the writing/design/debugging of source code then you're a programmer.
 
Top