Figuring out multiple file compilation with make & C - c

For a project, I was working with a partner writing a decision tree implementation. Since both of us are relative newcomers to C and had to work quickly, we basically dumped all the functionality in a single file, which ended up being over 1600 lines. It was a quick and dirty project to get working, but now the next assignment has us responsible for extending and re-implementing the code. In its current condition, that isn't going to happen.
Right now, I'm breaking up the original source based on function responsibility. Thing is, many of the functions are intertwined, and I'm getting major errors with my make file. More specifically, the other source files are reporting implicit declaration of functions that are declared in a separate file.
I really have no experience with multiple file makefiles. The current syntax is borrowed from a simple shell implmentation in last years Systems Programming class, although this current project is an order of magnitude greater in complexity.
cc= gcc
CFLAGS= -g -Wall -lm
proj2: main.o split.o tree.o id3.o output.o
$(CC) $(CFLAGS) -o proj2 main.o split.o tree.o id3.o output.o
I also tried a previous version where each object file was compiled separately like
main.o: main.c split.c tree.c id3.c output.c
$(CC) $(CFLAGS) -o main.c split.c tree.c id3.c output.c
and this repeated to create a .o file for each source, which then was compiled into an executable.
However, that didn't work and I got about 500 lines of compiler complaints and warnings, mainly about implicit function declarations.
So, essentially I have two related questions:
is it possible to intertwined function calls between different source files?
if so, how can I make it possible here?

First a word about your makefiles.
proj2: main.o split.o tree.o id3.o output.o
$(CC) $(CFLAGS) -o proj2 main.o split.o tree.o id3.o output.o
This should work (if the code is correct) but if you're using GNUMake (which you should) you can tidy it up:
proj2: main.o split.o tree.o id3.o output.o
$(CC) $(CFLAGS) -o $# $^
Now you have only one copy of the object list to maintain.
The other version is just wrong:
main.o: main.c split.c tree.c id3.c output.c
$(CC) $(CFLAGS) -o main.c split.c tree.c id3.c output.c
First, you're trying to compile all of the source files into one object file, which kind of defeats the purpose of object files. Second, you're naming your one object file main.o when that name should really belong to an object file made from main.cc. Third, the command tells the compiler to compile all of the other source files (split.c, tree.c, ...) into an object file called "main.c"-- not illegal, but you're bound to trip yourself up.
Also, you should try to use C++, not C, but that's for another day.
Now for breaking up the Big Ball of Mud. I assume you know how to break big functions into smaller ones, so the problem is segregating functions into different source files (and then compiling and linking them correctly). Suppose main() calls a function foo():
/* main.c */
void foo()
{
// do foo things
}
int main()
{
// do main things
foo();
return(0);
}
As you know, the foo must come first, otherwise the compiler would balk when main tried to call an undeclared function. But we can declare foo beforehand:
/* main.c */
void foo();
int main()
{
// do main things
foo();
return(0);
}
void foo()
{
// do foo things
}
When the compiler reaches the call to foo(), it already knows that such a function exists, and trusts us to define it later. Now here's the trick: if we instruct the compiler to compile, but not link (that is, produce an object file like main.o, not an executable like proj2), it will trust us even farther:
/* main.c */
void foo();
int main()
{
// do main things
foo();
return(0);
}
That will compile into main.o quite nicely. The compiler trusts us to provide the definition of void foo() in some other object file when we link things together into an executable. The definition will be in another file like so:
/* foo.c */
void foo()
{
// do foo things
}
We could build this by hand:
gcc -g -Wall -lm -c foo.c -o foo.o
gcc -g -Wall -lm -c main.c -o main.o
gcc -g -Wall -lm foo.o main.o -o proj2
But that gets tedious fast, so we'll write a makefile:
cc= gcc
CFLAGS= -g -Wall -lm
proj2: main.o foo.o
$(CC) $(CFLAGS) -o $# $^
%.o: %.c
$(CC) $(CFLAGS) -c -o $# $<
So far so good. If this much is clear then we can move on to header files...

You need to create header files for each of the source code to have the declarations in them. You then #include the appropriate header files at the top of the source code.

Related

make rules with existing target, existing prerequisites but no recipe

I was looking at the net-snmp code and I found something odd in the Makefile of the snmplib itself. The last couple of thousands of lines are nothing but rules in this form:
./dir_utils.lo: ../include/net-snmp/output_api.h
for EVERY library object and header file. Even those that are not compiled, depending on which flags are selected in the configure script.
I looked at the "make" manual but I didn't find this exact case. It may be (as stated in 5.9) that they do this to exclude the possibility that an implicit recipe is called on the target, but other than that I have no idea.
Another reason might be to "break" the compilation if the library is tampered with (deleting whatever header in the project causes in fact the makefile to crash because it can't execute the rule).
This is an educated guess but I would like to know the theory behind this. I mean the makefile already builds whatever it has to build, why include all these rules in explicit form?
Thanks
As I expect you know, the Makefile is generated by the ./configure script.
All the lines that interest you are auto-generated dependencies. Actually,
they are just the appended contents of the file Makefile.depend in the same
directory, which is part of the distribution and was generated with the aid
of gcc ahead of time.
So e.g.
./dir_utils.lo: ../include/net-snmp/output_api.h
just informs make of the vitally important fact that ./dir_utils.lo
depends on ../include/net-snmp/output_api.h. Then if ./dir_utils.lo
is older than ../include/net-snmp/output_api.h, make will re-make
./dir_utils.lo provided it has some recipe to do that, which it has.
Here is a project:
main.c
#include "hw.h"
#include <stdio.h>
int main(void)
{
puts(HW);
return 0;
}
hw.h
#ifndef HW_H
#define HW_H
#define HW "Hello World"
#endif
Makefile
CC := gcc
.PHONY: all clean
all: hw
hw: main.o
$(CC) -o $# $<
clean:
rm -f hw main.o
Build and run it:
$ make && ./hw
gcc -c -o main.o main.c
gcc -o hw main.o
Hello World
But there's a bug in the makefile. It doesn't know that main.o
depends on hw.h:
$ touch hw.h
$ make
make: Nothing to be done for 'all'.
Append that dependency to the makefile:
main.o: hw.h
and retry:
$ make
gcc -c -o main.o main.c
gcc -o hw main.o
Bug fixed.

Calling a function from another C file

I am writing a graphics program in C/C++ using OpenGL. I have a bunch of .c files and right now, I am trying to call a function from main.cpp in assignment1gui.c. The teacher has given me a file with the compile code. It looks like this:
OBJS = assignment1gui.o shadertools.o main.o
TARGET = ass1
CXX = gcc
DBFLAGS = -O0 -g3 -ggdb3 -fno-inline
WFLAGS = -Wall -ansi
GLFLAGS = `pkg-config --cflags gtk+-2.0`
LGLFLAGS = `pkg-config --libs gtk+-2.0` -lGL -lGLEW -lGLU -lglut
CXXFLAGS = $(WFLAGS) $(DFLAGS) $(GLFLAGS)
LDFLAGS = -export-dynamic -lXext -lX11 $(LGLFLAGS)
all: $(TARGET)
clean:
rm -f $(OBJS) $(TARGET)
.SUFFIXES: .cc
.cc.o:
$(CXX) -c $(CXXFLAGS) $<
.c.o:
$(CXX) -c $(CXXFLAGS) $<
$(TARGET): $(OBJS)
$(CXX) -o $(TARGET) $(OBJS) $(LDFLAGS)
The function I am trying to call is "drawPloygonLine()". I have declared this in "assignment1gui.h", and the definition, as I mentioned earlier, is in main.cpp.
From all the help on the Internet, I realized that there must some problem with the compile code. But I am not sure. Could someone please help me out?
Sorry I left out this part earlier. The errors I am getting are:
assignment1gui.o: In function `on_btn_color_color_set':
assignment1gui.c:(.text+0x1c): undefined reference to `drawPolygonLine'
assignment1gui.o: In function `on_rb_tool_line_toggled':
assignment1gui.c:(.text+0x8b): undefined reference to `drawPolygonLine'
assignment1gui.o: In function `on_rb_tool_circle_toggled':
assignment1gui.c:(.text+0xfc): undefined reference to `drawPolygonLine'
assignment1gui.o: In function `on_rb_tool_rect_toggled':
assignment1gui.c:(.text+0x16d): undefined reference to `drawPolygonLine'
assignment1gui.o: In function `on_rb_tool_edit_toggled':
assignment1gui.c:(.text+0x24a): undefined reference to `drawPolygonLine'
collect2: ld returned 1 exit status
make: *** [ass1] Error 1
The C/C++ files I have in the folder are main.cpp, assignment1gui.c and shadertools.c. I also have two header files- assignment1gui.h and shadertools.h. As I have mentioned earlier, I used the assignment1gui.h file to declare the new function "drawPolygonLine();"
Addition:
The declaration of the "drawPolygonLine();" is now in "main.h" and it looks like this:
#ifndef MAIN_H
#define MAIN_H
extern void drawPolygonLine();
#endif /* MAIN_H */
Both main.cpp and assignment1gui.c have "#include "main.h"" at their beginning. The definition of drawPolygonLine() is:
void drawPolygonLine()
{
if(option[opCount-1]==4 && drawn==false)
{
vCount++;
vertex[vCount].x=A;
vertex[vCount].y=B;
lines=lines+2;
vCount++;
vertex[vCount].x=polyX;
vertex[vCount].y=polyY;
clicks=0;
drawn=true;
}
}
All the variables used in the function are global in main.cpp.
An example of how I am calling drawPolygonLine(); in assignment1gui.c is in this function:
void on_btn_color_color_set(GtkColorButton *colorbutton, gpointer user_data)
{
drawPolygonLine();
GdkColor color;
gtk_color_button_get_color(colorbutton, &color);
printf("Color set rgb=(%i,%i,%i)\n",color.red,color.green,color.blue);
}
I hope this makes it clear.
The problem is that main.cpp isn't understood by this make file, since it only has rules for .cc or .c files. I suspect that main.o isn't being built - or, more likely, since you're getting as far as a failed compile, there is another file called main.c or main.cc which IS included in your program, but doesn't have drawPolygonLine() defined in it.
To solve this:
Remove or rename any existing main.c or main.cc files.
If your program is actually a C program, then change the filename to main.c
If your program is a C++ program, you could try changing the extension to .cc, but I suspect you'll then run in to problems since you're using gcc rather than g++. In this case, I'd also change:
CXX = gcc
to
CXX = g++
Remember that although a lot of C code is also valid C++ code, there are many subtle differences - and the best practices are very different in each language. For this reason, it's best to treat C and C++ as different languages.
If this doesn't fix it:
Check that the function signature of drawPolygonLine() declared in your header file is the same as the way it's defined in your main file. Check that these declarations match with the way you've called it.
Check that the files that call drawPolygonLine() have #include "assignment1gui.h" at the top
Check that drawPolygonLine() isn't declared as static (static functions aren't visible outside their translation unit - so the linker won't see them when compiling two output files together)
Also, it's good practice to have the function declaration in the header file corresponding to the source file it's defined in - so if you want to call a function in main.c from assignment.c, you would usually put the declaration in main.h, and #include it at the top of assignment.c. This won't be the cause of the problem that you're having, but it's good practice to get used to.

Compiling multiple source files

I have four files containing C code.
Headers.h - (contain all necessary) headers
AddStudent.h - that file include Headers.h also introduce some function delegation
AddStudent.c - contains functions described in AddStudent.h
main.c - contains main()
The question is how to compile the code with cc ?
In your case, your probably just need:
cc main.c AddStudent.c
The right thing to do is make a makefile. Here's a (probably a bit naive) example:
myapp: main.o AddStudent.o
cc -o myapp main.o AddStudent.o
main.o: main.c AddStudent.h Headers.h
cc -c -o main.o main.c
AddStudent.o: AddStudent.c AddStudent.h Headers.h
cc -c -o AddStudent.o AddStudent.c
The best place to learn about make is the GNU Make Manual.
Bonus note - if you're starting to learn C, you might want to check out clang. It gives way better error messages than gcc does, in addition to supporting C99 without special flags and being much faster at compiling.

'make' does not recompile when source file has been edited

I'm writing a little implementation of Conway's Game of Life in C. The source code is split in three files: main.c and functions.c/functions.h, where I put my functions definitions and declarations.
Now, to create a grid of cell, I have a matrix of this type:
Cell grid[GRID_HEIGHT][GRID_WIDTH];
where GRID_HEIGHT and GRID_WIDTH are constants defined in functions.h:
#define GRID_HEIGHT 10
#define GRID_WIDTH 10
The program runs fine, compiled with make and Makefile. But the problem is: if I try to change GRID_HEIGHT or GRID_WIDTH, when I run again my Makefile it says that all files are up-to-date!
I've tried to compile using the good ol' way gcc main.c etc. and it runs as it should. So, why make does not recompile the source?
This is my Makefile:
CC = gcc
OBJECTS = main.o functions.o
Game\ of\ Life : $(OBJECTS)
$(CC) $(OBJECTS) -o Game\ of\ Life -lncurses
%.o : %.c
$(CC) -c $<
Because you haven't told it that recompilation depends on functions.h.
Try adding this to your Makefile:
%.o : functions.h
Alternatively, modify your existing rule to be:
%.o : %.c functions.h
$(CC) -c $< -o $#
You've told make that .o files don't depend on .h files, so it doesn't recompile anything when a header changes.
Getting it to work right is hard (you need to generate dependencies for each .c file), but an easy way is just to define HEADERS which contains all your header files and make each .o file depend on all your headers.
If you're using GCC (well, you are), then it can be solved generically by passing -MD option to the compiler, GCC will generate a file containing Make dependencies on included headers:
CC = gcc
OBJECTS = main.o functions.o
%.o : %.c
$(CC) -MD -c $<
-include $(OBJECTS:.o=.d)
Some headers-related information can also be found in this question.

Undefined reference maybe makefile is wrong?

I had some issues earlier with declaring my array set of records. Now I think there is something wrong with my Makefile or something.
Here is my Makefile:
EEXEC = proj1
CC = gcc
CFLAGS = -c -Wall
$(EXEC) : main.o set.o
$(CC) -o $(EXEC) main.o set.o
main.o : main.h main.c
$(CC) $(CFLAGS) main.c
set.o : set.h set.c
$(CC) $(CFLAGS) set.c
There are more functions I have in my set.c file but these are the functions I am testing at the moment:
DisjointSet *CreateSet(int numElements);
DisjointSet *MakeSet(DisjointSet *S,int ele, int r);
void Print(DisjointSet *S);
And the errors I am receiving in the terminal is:
main.o: In function `main':
main.c:(.text+0x19): undefined reference to `CreateSet'
main.c:(.text+0x43): undefined reference to `MakeSet'
main.c:(.text+0x5f): undefined reference to `Print'
The errors that you're getting are linker errors, telling you that while linking your program the linker can't find a function named 'CreateSet' (etc.). It's not immediately obvious why that should be the case, because it appears that you're including "set.o" in the build command.
To troubleshoot build problems, it's often useful to figure out what make is trying to do, and then run the commands individually one at a time so you can see where things go wrong. "make -n" will show you what commands "make" would run, without actually doing them. I would expect to see a command like:
gcc -o proj1 main.o set.o
try running that by hand and see where it gets you.
Make sure you have included set.h in main.c
Also you declare EEXEC but use EXEC...
If these are all on one line in the makefile:
EEXEC = proj1 CC = gcc CFLAGS = -c -Wall
Then you have one macro EEXEC whose value is proj1 CC = gcc CFLAGS = -c -Wall, and you have no CC or CFLAGS macro. CC probably has a default, which is why that much is working.

Resources