Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to detect, a C-file is compiled directly into an executable?

I'd like to include a testing main-function in some of the C-files -- to allow them to be compiled into standalone little programs to independently test various functions.

But I don't want these multiple main-functions included in the real (big) executable/library.

Obviously, I can use my own define, such as -DINCLUDE_TEST_MAIN, but it occurred to me, that clang may already be telling me on its own. Somehow...

So, is there any way for the compiled code to detect, when it is compiled directly into an executable vs. when an object-file is being produced (with the -c flag)?

The solution needn't be universal -- I'm quite certain, a universal one does not exist -- my main compiler is clang...

like image 488
Mikhail T. Avatar asked Mar 11 '23 22:03

Mikhail T.


2 Answers

I don't see a better solution than -DINCLUDE_TEST_MAIN. Probably you could create some fancy command line that would strip main out of the object file if you don't need it, but I think the -D thing is the best way to go.

I don't really get what you mean with »but it occurred to me, that clang may already be telling me on its own. Somehow...« - if you fear a name clash, than just take a name, clang will definitively not use, like -DMIKHAIL_T_INCLUDE_TEST_MAIN; if this is not what you meant, then you should clarify that point.

Or, besides stuff.c, you could create stuff.main.c and test compile like so:

gcc stuff.c stuff.main.c -o stuff.test

(effectively moving main out of the file.)

like image 57
Bodo Thiesen Avatar answered Apr 25 '23 08:04

Bodo Thiesen


You could (but shouldn't) use the -Xlinker -zmuldefs option.

I'm posting this answer just as extra information as I don't think this method is the preferred one by far, but I find interesting knowing about it and it strongly relates to this very interesting question you asked.

This way the linker will ignore multiple definitions and it will take the first one it found in the first .c file.

I must stress though that I think that -zmuldefs should not be used, except in extreme cases where you know what you are doing and you have no better option.

I see the -Dxxx solution mentioned above as a far superior and it will also require extra parameters to the build (the only arguable minor advantage here is that you don't need to change parameters to clang by using the -zmuldefs aproach, gaining some transparency but I don't think this is actually any advantage as explained bellow).

And when you see a macro you show clear intention to the programmer that will maintain the code later. As opposed as looking for main() and finding it in thousands of files, knot knowing where it is actually used, except when you compile and debug.

Also by using the -zmuldefs you will need to pay attention in the order you write the files, because it will matter.

That said, if I have 2 files, lets say x1.c and x2.c, where

x1.c is:

#include <stdio.h>

#include "x2.h"

int x();

int main() {
    x();
}

int x() {
    static int isRecursiveCall_ = 0;

    printf("Hello, World 1!\r\n");

    if (!isRecursiveCall_) {
        isRecursiveCall_ = 1;
        main();
        x3();
        isRecursiveCall_ = 0;
    }
    return 0;
}

and x2.c is:

#include <stdio.h>

#include "x2.h"

int main() {
    printf("Hello, World 2!\r\n");
    x3();
    return 0;
}

int x3() {
    printf("Hello, World 3!\r\n");
    return 0;
}

Where x2.h is

#ifndef LINKTEST_X_H
#define LINKTEST_X_H

int main2();
int x2();
int x3();

#endif //LINKTEST_X_H

You can call clang x2.c -Xlinker -zmuldefs -o test.o and ./test.o will the output will be:

Hello, World 2!
Hello, Wolrd 3!

And then you can call clang x1.c x2.c -Xlinker -zmuldefs -o prog.o and ./prog.o and the result is:

Hello, World 1!
Hello, World 1!
Hello, World 3!

As desired.

Again, the only advantage here is not having to know when to use the -Dxxx parameter and there are many drawbacks.

In my example by using the -Dxxx method you would have to make it clear in the code what function you would be using. For instance if I change x2.c as follows:

#include <stdio.h>

#include "x2.h"

#ifdef _UNIT_TEST_ENABLED
int main() {
    printf("Hello, World 2!\r\n");
    x3();
    return 0;
}
#endif

int x3() {
    printf("Hello, World 3!\r\n");
    return 0;
}

Then when I build x2.c standalone version you'll need to call clang x2.c -DUNIT_TEST_ENABLED -o test.o, and when building all then you just have to call clang x1.c x2.c -o prog.o.

This make the intentions clear both in the code and in the build script and also allow to put the files in any order. Far better. Even better if you separate your unit tests in another file as also mentioned in previous answers.

In the end, having to know explicit build parameters and having an explicit #define or separate files, which is what you first wanted to avoid, is actually the best thing to do. There are times where transparency is good but others where it is evil.

like image 21
João Amaral Avatar answered Apr 25 '23 10:04

João Amaral