0

I am having a hard time linking a C++ class that I wrote with the a separate "test" C++ file. So I have these three files: threadtest.cc, Elevator.h, and Elevator.cc. In Elevator.h I define two classes: Passenger and Elevator. When I try to reference the functions for a Passenger or an Elevator from the threadtest.cc file, I get undefined reference errors. The first thing that I did was make sure that I had an include directive in threadtest.cc. I've been trying to search everywhere for a guide to linking and NACHOS classes in general, but the only thing I've been able to find is a Roadmap To NACHOS pdf, which lacks the detail required when issues like these arise.

I'm including the snippets of the parts from Elevator.h, threadtest.cc, and Makefile that may be the most relevant to this linking issue.

Below are the errors that I am most concerned about while compiling:

threadtest.o(.text+0x1c4):../threads/threadtest.cc:103: undefined reference to `Passenger::FromFloor()'
threadtest.o(.text+0x1d3):../threads/threadtest.cc:103: undefined reference to `Passenger::ToFloor()'
threadtest.o(.text+0x1e2):../threads/threadtest.cc:103: undefined reference to `Passenger::ID()'
threadtest.o(.text+0x204):../threads/threadtest.cc:104: undefined reference to `Elevator::Request(Passenger*)'
threadtest.o(.text+0x215):../threads/threadtest.cc:106: undefined reference to `Passenger::ID()'
threadtest.o(.text+0x237):../threads/threadtest.cc:107: undefined reference to `Elevator::LoadPassenger(Passenger*)'
threadtest.o(.text+0x248):../threads/threadtest.cc:109: undefined reference to `Passenger::ID()'
threadtest.o(.text+0x26a):../threads/threadtest.cc:110: undefined reference to `Elevator::UnloadPassenger(Passenger*)'

Here are the lines in question from threadtest.cc starting at line 97 t0 111:

#include "Elevator.h"

class Passenger;
class Elevator;

void    
RunPassenger( int ptr )
{
  int *temp = (int*) ptr;
  Passenger *P = new Passenger( temp[ 0 ], temp[ 1 ], temp[ 2 ] );

  printf( "Person %d wants to go to floor %d from floor %d\n", P->ID( ), P->ToFloor( ), P->FromFloor( ) );
  E->Request( P );

  printf( "Person %d got into the elevator\n", P->ID( ) );
  E->LoadPassenger( P );

  printf( "Person %d got out of the elvator\n", P->ID( ) );
  E->UnloadPassenger( P );
}

Here is my definition of the classes in Elevator.h

#ifndef ELEVATOR_H
#define ELEVATOR_H

#include "copyright.h"
#include "thread.h"
#include "list.h"
#include "synch.h"

class Passenger
{
  private:
    int fromFloor;
    int toFloor;
    int tID;
    bool passengerDirection;

  public:
    Passenger( int fromFloor, int toFloor, int tID );
    int FromFloor( );
    int ToFloor( );
    int ID( );
    bool pDirection( );
};

class Elevator
{
  public:
    Elevator( );
    Elevator( int numFloors );
    ~Elevator( );
    int TotalFloors( int numFloors );
    Thread* ThreadPtr( );
    bool IsAtCapacity( );
    bool PassengerDirection( bool direction );
    void LoadPassenger( Passenger *P );
    void UnloadPassenger( Passenger *P );
    void PassengersWaiting( );
    void Run( );
    void Initialize( );
    void AddToQueue( Passenger *P );
    void Request( Passenger *P );

  private:
    Thread *T;
    List *Queue, *UpQueue, *DownQueue;
    Semaphore *S;
    int NumFloors;
    int* PassCounter;
    int OnBoard;
    bool Direction;
    int CurrentFloor;
    Condition *ElevatorCV;
    Condition *PassengerCV;
    Lock *lock;
};

#endif

Finally, there is a GNU Makefile that is used to compile everything in NACHOS. Here are the portions of the Makefile that correspond to threadtest.cc and Elevator.h

threadtest.o: ../threads/threadtest.cc ../threads/copyright.h \
  ../threads/system.h ../threads/utility.h ../threads/bool.h \
  ../threads/Elevator.h ../threads/Elevator.cc \
  ../machine/sysdep.h ../threads/copyright.h /usr/include/stdio.h \
  /usr/include/features.h /usr/include/sys/cdefs.h \
  /usr/include/gnu/stubs.h \
  /usr/lib/gcc/i386-redhat-linux/3.4.6/include/stddef.h \
  /usr/include/bits/types.h /usr/include/bits/wordsize.h \
  /usr/include/bits/typesizes.h /usr/include/libio.h \
  /usr/include/_G_config.h /usr/include/wchar.h /usr/include/bits/wchar.h \
  /usr/include/gconv.h ../threads/stdarg.h /usr/include/bits/stdio_lim.h \
  /usr/include/bits/sys_errlist.h /usr/include/string.h \
  /usr/include/xlocale.h ../threads/thread.h ../threads/scheduler.h \
  ../threads/list.h ../machine/interrupt.h ../threads/list.h \
  ../machine/stats.h ../machine/timer.h ../threads/utility.h

Elevator.o: ../threads/Elevator.cc ../threads/threadtest.h ../threads/copyright.h \
  ../threads/utility.h ../threads/system.h ../threads/utility.h ../threads/bool.h \
  ../machine/sysdep.h /usr/include/stdio.h \
  /usr/include/features.h /usr/include/sys/cdefs.h \
  /usr/include/gnu/stubs.h \
  /usr/lib/gcc/i386-redhat-linux/3.4.6/include/stddef.h \
  /usr/include/bits/types.h /usr/include/bits/wordsize.h \
  /usr/include/bits/typesizes.h /usr/include/libio.h \
  /usr/include/_G_config.h /usr/include/wchar.h /usr/include/bits/wchar.h \
  /usr/include/gconv.h ../threads/stdarg.h /usr/include/bits/stdio_lim.h \
  /usr/include/bits/sys_errlist.h /usr/include/string.h \
  /usr/include/xlocale.h ../threads/thread.h ../threads/scheduler.h \
  ../threads/list.h ../machine/interrupt.h ../threads/list.h \
  ../machine/stats.h ../machine/timer.h ../threads/utility.h

Here are the definitions of all the functions in Elevator.cc

#include "Elevator.h"
#include "thread.h"
#include "Condition.h"
#include "synch.h"

#define MAX_CAPACITY 5

//----------------------------------------------------------------------
// Elevator::Elevator
//  Initialize an elevator, without any parameters
//----------------------------------------------------------------------
Elevator::Elevator( )
{
  T = new Thread( "Elevator" );
  S = new Semaphore( "Semaphore", 1 );
  Queue = new List; UpQueue = new List; DownQueue = new List;
  OnBoard = 0;
  CurrentFloor = 0;
}

//----------------------------------------------------------------------
// Elevator::Elevator
//  Initialize an elevator.
//
//  "NumFloors" is the total number of floors an elevator will traverse
//  "T" is a new Thread for an elevator
//  "S" is a Semaphore that is used for synchronization of elevator
//  "Queue" is a List that holds Passengers in the elevator
//  "UpQueue" and "DownQueue" hold Passengers waiting for elevator
//  "OnBoard" is a counter of the Passengers on the elevator
//  "CurrentFloor" is a counter of the floors the elevator has visited
//----------------------------------------------------------------------
Elevator::Elevator( int numFloors ) : NumFloors( numFloors )
{
  T = new Thread( "Elevator" );
  S = new Semaphore( "Semaphore", 1 );
  Queue = new List; UpQueue = new List; DownQueue = new List;
  OnBoard = 0;
  CurrentFloor = 0;
}

//----------------------------------------------------------------------
// Elevator::Elevator
//  Initialize an elevator.
//
//  "NumFloors" is the total number of floors an elevator will traverse
//  "T" is a new Thread for an elevator
//  "S" is a Semaphore that is used for synchronization of elevator
//  "Queue" is a List that holds Passengers in the elevator
//  "UpQueue" and "DownQueue" hold Passengers waiting for elevator
//  "OnBoard" is a counter of the Passengers on the elevator
//  "CurrentFloor" is a counter of the floors the elevator has visited
//----------------------------------------------------------------------
Elevator::~Elevator( )
{
  delete T;
  delete S;
  delete Queue;
  delete UpQueue;
  delete DownQueue;
}

//----------------------------------------------------------------------
// Elevator::TotalFloors
//  Initialize the NumFloors variable in the Elevator class.
//----------------------------------------------------------------------
void
Elevator::TotalFloors( int numFloors )
{
  this->NumFloors = numFloors;

  PassCounter = new int[ numFloors + 1 ];
  for( int i = 1; i < numFloors; i++ )
    PassCounter[ i ] = 0;
}

//----------------------------------------------------------------------
// Elevator::ThreadPtr
//  Returns the Thread responsible for the Elevator functions
//----------------------------------------------------------------------
Thread*
Elevator::ThreadPtr( )
{
  return T;
}

//----------------------------------------------------------------------
// Elevator::IsAtCapacity
//  Determine if the Elevator has reached its maximum capacity. Returns
//  a boolean value.
//----------------------------------------------------------------------
bool
Elevator::IsAtCapacity( )
{
  if( this->OnBoard < MAX_CAPACITY )
    return false;

  return true;
}

//----------------------------------------------------------------------
// Elevator::PassengerDirection
//  Determines if the elevator and a passenger are heading in the
//  different directions. Returns a boolean value of true if they are
//  and a boolean value of false if headed in the same direction.
//
//  "direction" is a boolean representing the passenger's direction
//----------------------------------------------------------------------
bool
Elevator::PassengerDirection( bool direction )
{
  if( direction && this->Direction )
    return false;
  if( direction || this->Direction )
    return true;

  return false;
}

//----------------------------------------------------------------------
// Elevator::LoadPassenger
//  Load a new passenger onto the elevator. First check if the elevator
//  is at capacity and if the passenger is traveling in the same
//  direction. The elevator uses a semaphore before loading the
//  passenger onto its queue, and releases the semaphore when it's done.
//
//  "p" is a pointer to a passenger struct to extract passenger info
//----------------------------------------------------------------------
void
Elevator::LoadPassenger( Passenger *P )
{
  if( this->IsAtCapacity( ) || this->PassengerDirection( P->pDirection( ) ) )
    return ;

  lock->Acquire( );
  AddToQueue( P );


  while( CurrentFloor != P->ToFloor( ) )
    PassengerCV->Wait( lock );
  lock->Release( );
}

//----------------------------------------------------------------------
// Elevator::UnloadPassenger
//  Unload a passenger from the elevator. First check if the passenger's
//  destination is the same as the current floor. If so, unload the
//  passenger and decrement the number of passengers on board. If not,
//  then re-add the passenger to the queue at the front of the list.
//
//  "p" is a pointer to a passenger struct to extract passenger info
//----------------------------------------------------------------------
void
Elevator::UnloadPassenger( Passenger *P )
{
  lock->Acquire( );


  if( PassCounter[ CurrentFloor ] == 0 )
    ElevatorCV->Signal( lock );

  lock->Release( );
}

//----------------------------------------------------------------------
// Elevator::PassengersWaiting
//  Determine if a passenger is queued waiting to go up or waiting to go
//  down. Depending on the elevator's direction, a passenger will be
//  picked up, if one is queued. If one is not queued, then the function
//  terminates.
//----------------------------------------------------------------------
void
Elevator::PassengersWaiting( )
{
  Passenger *p = 0;
  S->P( );
  if( Direction )
    if( !UpQueue->IsEmpty( ) )
    {
      p = ( (Passenger *)UpQueue->Remove( ) );
    }
  if( !Direction )
    if( !DownQueue->IsEmpty( ) )
    {
      p = ( (Passenger *)DownQueue->Remove( ) );
    }

  if( p != 0 && !PassengerDirection( p->pDirection( ) ) )
    LoadPassenger( p );
  S->V( );
}

//----------------------------------------------------------------------
// Elevator::Run
//  Starts the elevator at floor 1 and runs until floor "NumFloors." The
//  elevator prints the floor number when it arrives. It then loops
//  through the first passenger in the queue, removing that passenger,
//  while the passenger's destination is the current floor. Once all
//  passengers are unloaded, we check if the elevator can accommodate
//  any additonal passengers.
//----------------------------------------------------------------------
void
Elevator::Run( )
{
  ASSERT( OnBoard >= 0 );
  while( 1 )
  {
    lock->Acquire( );
    if( Queue->IsEmpty( ) )
      ElevatorCV->Wait( lock );
    lock->Release( );

    while( !( Queue->IsEmpty( ) ) )
    {
      for( int i = 0; i < 50; i++ )
        currentThread->Yield( );

      Person *p = Queue->Remove( );
      Direction = p->pDirection( );

      lock->Acquire( );
      if( Direction )
        CurrentFloor--;
      else if( !Direction )
        CurrentFloor++;

      printf( "Elevator arrives on floor %d\n", CurrentFloor );
      if( CurrentFloor == p->ToFloor )
      {
        delete p;
        PassengerCV->Broadcast( lock );
        ElevatorCV->Wait( lock );
      }

      lock->Release( );
    }

    lock->Acquire( );
    List *TempList = Queue;
    if( Direction )
    {
      Queue = DownQueue;
      DownQueue = TempList;
    }
    else if( !Direction )
    {
      Queue = UpQueue;
      UpQueue = TempList;
    }
    lock->Release( );
  }
}

//----------------------------------------------------------------------
// Elevator::AddToQueue
//  Adds a passenger to the UpQueue or the DownQueue from the ThreadTest
//  file. 
//----------------------------------------------------------------------
void
Elevator::AddToQueue( Passenger *P )
{
  if( Queue->IsEmpty( ) )
  {
    Queue->SortedInsert( (void*) P, P->ToFloor( ) );
    OnBoard++;
  }
  else
  {
    Person *P2 = (Person*) Queue->Remove( );
    if( P->FromFloor( ) != CurrentFloor )
    {
      if( P2->FromFloor( ) < P->FromFloor( ) )
        UpQueue->Append( (void*) P );
      else 
      {
        Queue->SortedInsert( (void*) P, P->ToFloor( ) );
        OnBoard++;
      }
    }
    else
    {
      if( P->FromFloor( ) < CurrentFloor )
        DownQueue->Append( (void*) P );
      else
      {
        Queue->SortedInsert( (void*) P, P->ToFloor( ) );
        OnBoard++;
      }
    }

    Queue->SortedInsert( (void*) P2, P2->ToFloor( ) );
    OnBoard++;
  }
}

void
Elevator::Request( Person *P )
{
  lock->Acquire( );
  bool Enter = true;

  if( !( Queue->IsEmpty( ) ) )
    if( P->FromFloor != CurrentFloor )
      Enter = false;

  if( CurrentFloor == P->FromFloor( ) && Enter )
  {
    lock->Release( );
    return;
  }

  AddToQueue( P );
  while( CurrentFloor != P->FromFloor )
    PassengerCV->Wait( lock );

  lock->Release( );
}

//----------------------------------------------------------------------
// Passenger::Passenger
//  Initialize a passenger and load the Passenger class' private
//  variables.
//
//  "atFloor" is an int representing a passenger's origin floor
//  "goToFloor" is an int representing a passenger's destination floor
//  "PID" is an int representing a passenger's ID
//----------------------------------------------------------------------
Passenger::Passenger( int fromFloor, int toFloor, int tID ): toFloor( toFloor ), fromFloor( fromFloor ), tID( tID )
{
  passengerDirection = fromFloor < toFloor;
}

//----------------------------------------------------------------------
// Passenger::ToFloor
//  Returns the value of the "toFloor" variable, which represents the
//  floor a Passenger wants to reach.
//----------------------------------------------------------------------
int
Passenger::ToFloor( )
{
  return this->toFloor;
}

//----------------------------------------------------------------------
// Passenger::FromFloor
//  Returns the value of the "fromFloor" variable, which represents the
//  floor a Passenger started on.
//----------------------------------------------------------------------
int
Passenger::FromFloor( )
{
  return this->fromFloor;
}

//----------------------------------------------------------------------
// Passenger::ID
//  Returns the value of the "tID" variable, which represents a
//  Passenger's unique identification number. Also, a thread ID for each
//  Passenger/Thread.
//----------------------------------------------------------------------
int
Passenger::ID( )
{
  return this->tID;
}

//----------------------------------------------------------------------
// Passenger::pDirection
//  Returns the value of the "passengerDirection" variable, which
//  represents which direction a Passenger is headed. A true value means
//  that a Passenger is headed up and a false value means that a
//  Passenger is headed down. 
//----------------------------------------------------------------------
bool
Passenger::pDirection( )
{
  return passengerDirection;
}

Any kind of help or idea will be GREATLY appreciated! I have invested in the C++ Programming Language by Bjarne Stroustrup in hopes that it will help with these kinds of questions in the future.

wmarquez
  • 147
  • 1
  • 1
  • 8

4 Answers4

1

As I recall you had to edit the Makefile.common in /code, then run a gmake from there which automatically generates the directory-level makefiles in NACHOS.

If you open up Makefile.common, you'll see these lines:

THREAD_H =../threads/copyright.h\
../threads/list.h\
../threads/scheduler.h\
../threads/synch.h \
../threads/synchlist.h\
../threads/system.h\
../threads/thread.h\
../threads/utility.h\
../machine/interrupt.h\
../machine/sysdep.h\
../machine/stats.h\
../machine/timer.h

THREAD_C =../threads/main.cc\
../threads/list.cc\
../threads/scheduler.cc\
../threads/synch.cc \
../threads/synchlist.cc\
../threads/system.cc\
../threads/thread.cc\
../threads/utility.cc\
../threads/threadtest.cc\
../machine/interrupt.cc\
../machine/sysdep.cc\
../machine/stats.cc\
../machine/timer.cc

THREAD_S = ../threads/switch.s

THREAD_O =main.o list.o scheduler.o synch.o synchlist.o system.o thread.o \
utility.o threadtest.o interrupt.o stats.o sysdep.o timer.o

You'll want to add lines to compile your Elevator class here. Have you done that?

Francesca Nannizzi
  • 1,695
  • 13
  • 18
  • Yes, this is definitely correct! I guess I didn't notice that the Makefile was including a Makefile.common and Makefile.dep from a higher directory. Once I added the necessary lines in Makefile.common everything worked wonderfully! – wmarquez Oct 05 '13 at 21:33
0

Where is your implementation of the methods like ToFloor() etc? Your Elevator.h defines the interface to the Elevator class. You need to link in the implementation (i.e. to have Elevator.o in your link line in the Makefile).

So, in your Makefile you will have:

threadtest:    threadtest.o Elevator.o
    $(CXX) $(CXXFLAGS) -o $@ threadtest.o Elevator.o
Alexander L. Belikoff
  • 5,698
  • 1
  • 25
  • 31
  • Sorry, I completely forgot to add the implementations. I have those in Elevator.cc, but how to I link the implementations to the rest of the code? I think this is where I have the biggest confusion. – wmarquez Oct 04 '13 at 20:48
0

Do all the functions is question (Request, FromFloor, ToFloor etc) have a body? Have you defined hte functions? If you have already defined them, are you compiling the definitions and linking them together?

Crusher
  • 64
  • 6
  • Yes, I defined these functions in the Elevator.cc class, but I am not 100% sure how to compile the definitions and link them together. – wmarquez Oct 04 '13 at 20:51
0

The methods of Passanger are not implemented. You need to provide them

Passenger( int fromFloor, int toFloor, int tID );
int FromFloor( );
int ToFloor( );
int ID( );
bool pDirection( )

I would also object to the coding style of putting more class definitions in the same file but that is a coding style preference.

The forward declarations after the include elevator header are not required as well.

class Passenger; class Elevator;

Dragos
  • 121
  • 5
  • I put the implementation for these Passenger class methods at the bottom of the Elevator.cc file (i.e. after all the Elevator class methods). I noticed some examples within the NACHOS code itself that uses this style and I simply chose to copy it. Honestly, I thought having less classes would avoid any linking issues that could have occurred. But I guess I'm still in the same boat. – wmarquez Oct 04 '13 at 20:55
  • To avoid the worry of the make file syntax why not quickly slap the code in Eclipse CDT or another IDE make sure it works then and afterwards write a make file or automake autoconf file. – Dragos Oct 04 '13 at 21:10