3

I have a project with a handful of files: source1.c source2.c source1.h source2.h. Now source1.c declares some variables and source1.h externs them (with a conditional build macro). source2.c would then use the extern variables by assigning a value to them.

The issue is that source1.h has something like the following:

#ifdef CUST1
extern type var_name;
#else
// Need to extern something or use a clever macro here but i dont like these below
//#define var_name int x (requires top of scope block usage)
//#define var_name // (doesnt compile)
//#define var_name -1 (simply doesnt compile)
//#define var_name 1= (This seems like it would reduce to unused 1==1; but doesnt work)
#endif

My build works for CUST1 but it will not work for CUST2 because var_name is never declared/outta scope when it is referenced in source2.c.

I dont want to use var_name for CUST2 and it is unreachable code in source2.c. Here is my question, how can i use a macro to #define var_name so that the assignment "goes away" or does nothing?

I could "#define var_name int x". This would put int x on the stack in the source2.c functions and assign the values in source2.c, but if it is ever referenced anywhere besides the top of a scope block, my old (C89?) compiler will error.

For example if source2.c ever had the following it would fail to compile:

unsigned short local_var = 0;
local_var = 1; //or someother value
var_name = local_var * 2;

I could wrap the logic in source2.c with the same #ifdef CUST1 macros but that seems not so good either.

If var_name was only being compared against it would not be so bad because i could just use #define var_name -1 or something that would fail all compares/switches. The issues is -1 = temp_var; does not compile because -1 cant be an lvalue.

Similiarly i cant "#define var_name //" because comments are removed before the marcos are replaced according to this:Can you #define a comment in C?

Is there a clever macro trick that will hide/remove this assignment, that does not put a local_var on the stack? I feel like there is something possible with a ternary but i cant think of it.

EDIT minimal example code:

source1.c

int var_name = 0;

source1.h

#ifdef CUST1
extern int var_name;
#else
// clever macro
#endif

source2.c

#include "source1.h"
int main(){
  var_name = 1;
  return 0;
}
Bwebb
  • 675
  • 4
  • 14
  • 3
    ["Clever" code is bad code.](https://softwareengineering.stackexchange.com/questions/91854/how-to-train-yourself-to-avoid-writing-clever-code). Do not hide confusing code with another level of confusion. **FIX** the fundamental problem. – Andrew Henle May 16 '18 at 01:00
  • 1
    If `var_name` is CUST1 only then you need to `#ifdef` all of it's use OR just use it in most places and just #ifdef the places where you don't want it visible. – John3136 May 16 '18 at 01:05
  • @AndrewHenle I agree with that, the fundamental problem is that it is assigned when the project is compiled for CUSTOMER2. The "fundamental FIX" is to wrap the logic where it is used in #ifdef CUST1 macros but that doesnt scale very well. There are more than two customers and now that var_name is externed it may be used elsewhere going forward. I do agree with you that this over-complicates things but thats the way it goes. The question is about a macro technique that hides the assignment for CUSTOMER2. – Bwebb May 16 '18 at 01:08
  • 1
    Please clarify whether you want the assignment transformed to (a) a no-op, (b) a compilation error, or (c) a runtime error – M.M May 16 '18 at 02:50
  • Also if C89 is a requirement, mention that too – M.M May 16 '18 at 02:51
  • @M.M anyway I think it's a bad design. OP wants a flexible code, but tailor made functions for different customers should have been designed as different functional blocks but not only tricky variables (which would greatly increase the difficulty for maintenance). – Stan May 16 '18 at 03:24
  • @M.M I would prefer no-op but run time error would be ok since its unreachable. caf's answer below is interesting. It does have to work for C89. I would prefer it work for both sides of an assignment, but lvalue is required. – Bwebb May 16 '18 at 04:15
  • @stan I agree its a bad design. This is a giant code base with a lot of customers. I didn't design it. This variable being externed is for a state machine flag that some customers should not be using. I dont want to #ifdef the assignments with macros i would rather have them converted to no-op when it doesnt apply. – Bwebb May 16 '18 at 04:15
  • 1
    In C99 you can do `#define var_name (int[]){0}[0]` – M.M May 16 '18 at 04:22
  • @Bwebb then what should the no-op do when `var_name` works as an rvalue (e.g. somewhere you have `x = 1 + var_name * 2`)? – Stan May 16 '18 at 06:00

3 Answers3

1

There is no direct way of hiding an assignment as you propose, but there are some alternatives:

  1. You could use another macro:

    #ifdef CUST1
    extern type var_name;
    /* Do the real assignment */
    #define SET_VAR_TO(val) do{ var_name = (val); } while(0)
    #else
    /* Just evaluate for the side-effects */
    #define SET_VAR_TO(val) do { val; } while(0)
    #endif
    

    And then inside source2.c replace all assignments to var_name with SET_VAR_TO(value) like:

    int foo(void) {
        /* Replace
        var_name = bar();
        * With: */
        SET_VAR_TO(bar());
    }
    
  2. You could test if CUST1 is defined also in source2.c:

    /* in source2.c */
    int foo(void) {
    #ifdef CUST1
    var_name = bar();
    #endif
    }
    

    In this case you could even wrap the assignment to a real function only defined in the source file:

    /* in source2.c */
    int set_var_to(type value) {
    #ifdef CUST1
    var_name = value;
    #endif
    }
    
    int foo(void) {
        /* Replace
        var_name = bar();
        * With: */
        set_var_to(bar());
    }
    

As you prefer to not duplicate code by wrapping each assignment inside an #ifdef CUST1 ... #endif you could use the function or the macro. Remember that the option #1 will also expose the macro SET_VAR_TO to any file that #includes source1.h, not just source2.c.

  • Thanks for the response. If i could "#define var_name 1=" then in main it would be 1==1; which would have no affect and "absorb" the assignment in the CUST2 case. This doesnt seem to work for me yet though. Both of your solutions work and if i cant think of anything better ill probably implement the first one and credit your response as the answer, but im still not convinced there isn't something clever to absorb the assignment. – Bwebb May 16 '18 at 01:20
  • `set_var_to()` could even be a `static inline` function in `source1.h` so the function call compiles away to nothing in the `!CUST1` case. – caf May 16 '18 at 03:29
1
extern int varname;

#define varname __attribute__((unused)) int varname

int square(int num) {
    varname = 1;
    return num * num;
}

It will work at any optimization level except the -O0

https://godbolt.org/g/phssNn

0___________
  • 60,014
  • 4
  • 34
  • 74
  • Thanks for the response peter. Would this still throw an compiler error for C89 if it was not at the top of a scope block? It looks like its declaring it unused, but its still declaring it and declarations in the middle of a scope block fail compilation for some of my customers. – Bwebb May 16 '18 at 01:26
  • Thee I'd no other way in the assignment. BTW I do not understand what for – 0___________ May 16 '18 at 01:36
1

If the code accessing var_name in source2.c is unreachable when CUST1 is not defined (as you seem to say), then you can define it like this:

#include <stdlib.h>

#define var_name (*(abort(), (type *)0))

This will work syntactically as an lvalue (or rvalue) of the correct type, but will abort the program if it's actually ever executed.

caf
  • 233,326
  • 40
  • 323
  • 462