简体   繁体   中英

How to determine if a declared extern variable is defined or initialized in some C file

I have some table of structs that get compiled differently depending on what driver I am using. I want to be able to check if a certain driver has been compiled (or by relation, if its table is defined).

I've look all over but I can't seem to find a way to determine if a declared extern variable has been defined or if there's some way to check if ac source file has been compiled (and linked) within a given application. I had looked at using some macro magic like the container_of macro however I've so far come up short.

for example say I have the following:

checkFile.c

#include "neutral.h"
#include "fileA.h"
#include "fileB.h"

bool setTable(int type, someStruct_t *table) {

    /*
    This doesn't work
    fails to compile with:
    undefined reference to fileA_Table
    ironically this is what I'm trying to check
    */
    switch (type) {
        case FILEA:
            if (fileA_Table) {
                someTable = &fileA_Table;
                return true;
            }
            else {
                someTable = NULL;
                return false; 
            }
        break;
        case FILEB:
            if (fileB_Table) {
                someTable = &fileB_Table;
                return true;
            }
            else {
                someTable = NULL;
                return false; 
            } 
        break;
        default:
            someTable = NULL;
            return false;   
    }
}

neutral.h

typedef struct {
    int memberA;
    bool memberB;
} someStruct_t;

extern someStruct_t fileA_table[];
extern someStruct_t fileB_table[];

fileA.c

#include "neutral.h"

someStruct_t fileA_table[] = {
    {
        .memberA = 0;
        .memberB = false;
    },
    {
        .memberA = 5;
        .memberB = false;
    }
}

fileB.c

#include "neutral.h"
someStruct_t fileB_table[] = {
    {
        .memberA = 14;
        .memberB = true;
    },
    {
        .memberA = 5;
        .memberB = true;
    }
}

I'm not even sure that this is something that I can do in C and really the fundamental problem I'm trying to solve is initializing some type of interface that relies on fileA or fileB and ensuring which arrays are available to use from fileA and/or fileB. Note this

Ideally I'd actually like if I could just use file_table but I don't think that's possible if both fileA and fileB are compiled.

Note this should work regardless of if fileA or fileB or both files are compiled.

Any help is much appreciated.

Your code can safely assume that the variable exists.

If it doesn't, you'll get an error from the compiler during the linking stage when you attempt to link your object files into an executable.

If you want some kind of conditional compilation, you'll need to set a macro based on what's available and then check for that macro in various parts of your code.

For example:

#include "neutral.h"

#ifdef USE_FILE_A
#include "fileA.h"
#elif defined USE_FILE_B
#include "fileB.h"
#endif

bool setTable(someStruct_t **table)
{
#ifdef USE_FILE_A
    *table= &fileA_Table;
    return true;
#elif defined USE_FILE_B
    *table= &fileB_Table;
    return true;
#else
    *table = NULL;
    return false;
}

With the updated code:

#include "neutral.h"

#ifdef HAS_FILE_A
#include "fileA.h"
#endif
#ifdef HAS_FILE_B
#include "fileB.h"
#endif

bool setTable(int type, someStruct_t *table)
{
    switch (type) {
        case FILEA:
            #ifdef HAS_FILE_A
                someTable = &fileA_Table;
                return true;
            #else
                someTable = NULL;
                return false; 
            #endif
        break;
        case FILEB:
            #ifdef HAS_FILE_B
                someTable = &fileB_Table;
                return true;
            #else
                someTable = NULL;
                return false; 
            #endif
        break;
        default:
            someTable = NULL;
            return false;   
    }
}

Based on Bill Lynch's suggestion I looked into strong and weak symbols here and here and found an appealing solution. Roughly this is what I came up with.

checkFile.c

#include "neutral.h"
#include "fileA.h"
#include "fileB.h"

bool setTable(int type, someStruct_t *table) {
    /*
     * This will compile and if fileA_table has not been
     * defined in fileA then it will evaluate to 0
     */
    switch (type) {
        case FILEA:
            if (fileA_Table) {
                someTable = &fileA_Table;
                return true;
            }
            else {
                someTable = NULL;
                return false; 
            }
        break;
        case FILEB:
            if (fileB_Table) {
                someTable = &fileB_Table;
                return true;
            }
            else {
                someTable = NULL;
                return false; 
            } 
        break;
        default:
            someTable = NULL;
            return false;   
    }
}

neutral.h

typedef struct {
    int memberA;
    bool memberB;
} someStruct_t;

__attribute__((weak)) extern someStruct_t fileA_table[];
__attribute__((weak)) extern someStruct_t fileB_table[];

fileA.c

#include "neutral.h"

someStruct_t fileA_table[] = {
    {
        .memberA = 0;
        .memberB = false;
    },
    {
        .memberA = 5;
        .memberB = false;
    }
}

fileB.c

#include "neutral.h"
someStruct_t fileB_table[] = {
    {
        .memberA = 14;
        .memberB = true;
    },
    {
        .memberA = 5;
        .memberB = true;
    }
}

I like this since it meant I could get the results I want with minimal impact to my interface and no impact to compiler options/commands.

I would have liked to use the same symbol for fileA and fileB to make the generic interface layer and the neutral header cleaner to get something like this:

neutral.h

typedef struct {
    int memberA;
    bool memberB;
} someStruct_t;

__attribute__((weak)) extern someStruct_t file_table[];

checkFile.c

#include "neutral.h"
#include "fileA.h"
#include "fileB.h"

bool setTable(int type, someStruct_t *table) {

    if (file_Table) {
        someTable = &file_table;
        return true;
    }
    
    someTable = NULL;
    return false; 
}

This could be done so long as we compile the interface starting at checkFile.c as two seperate binaries. Once linking with fileA.c and once with fileB.c. This does add some complexity in build options, increases code size, and I'm not sure of the impact to performance however it makes the code more maintainable and much more so should there be several potential files and several types of tables that need to be checked.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM