[C/C++] typedef 함수 포인터 구현원리

프로그래밍 팁 2015.04.06 13:33

 최근 안드로이드 프레임워크를 공부하다보니 JAVA는 물론 JNI를 통해 연결되는 C/C++ 코드들에 대해 빠삭하게 공부를 하고 있습니다. 정말이지 흔히 쓰는 저 언어들에 슬슬 도가 트고 있지 않은가 싶을정도로 자신의 실력에 대해 자만심이 들기도 할 정도입니다.

 소스코드들을 공부하는 과정에서 어려운 부분이 있다면 바로 흔히 사용하지 않는 방식으로 구현된 소스코드를 해석하는 때라고 생각합니다. 특히 수업시간에는 이론만 알고 넘어가는 함수 포인터라는 생소한 개념이 쓰였을 때는 처음엔 이것의 정체 조차 모르는 경우도 허다하지요.


 본론으로 들어가기에 앞서 간단한 소스코드를 통하여 함수 포인터에 대한 기념을 알아보도록 하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<stdio.h>
 
void (*ptrfunc)(int);
 
void testprint(int n){
    printf("Number : %d\n", n);
}
 
int main(){
    testprint(100);
    ptrfunc = testprint;
    ptrfunc(77);
 
    return 0;
}
cs



위의 결과 출력을 보시면 대략적인 함수 포인터의 동작 원리를 이해하실 수 있을 것이라 생각합니다.

여기서 잠시 코드를 좀 더 자세히 설명 드리도록 하겠습니다.


void (*ptrfunc)(int);

함수 포인터는 위에서 보시는 바와 같은 구조로 이루어져 있습니다. 각 부분의 기능은 다음과 같습니다.

return값의 자료형 (*포인터 함수의 이름) (인자값)


 함수 포인터를 사용하실 때 주의하실 점은 함수 포인터가 이용하고자 하는 함수의 return값의 자료형과 인자값의 자료형 및 갯수가 일치해야 사용할 수 있다는 점입니다. 다음 코드를 확인해 봅시다.


ptrfunc = testprint;


 함수 포인터에 사용하고자 하는 함수의 이름을 입력합니다. 위 과정을 통해 기존 포인터와 같이 함수의 주소값이 포인터에 저장됨으로서 해당 함수 포인터는 자신이 가지고 있는 주소값의 함수와 같은 기능을 구현하게 됩니다.


 다음으로 typedef가 사용된 함수 포인터에 대해 살펴보도록 합시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
 
typedef void (*ptrfunc)(int);
 
void testprint(int n){
    printf("Number : %d\n", n);
}
 
int main(){
    testprint(100);
    ptrfunc elecs;
    elecs = testprint;
    elecs(77);
 
    return 0;
}
cs


 위에서 설명하였던 소스코드에 typedef를 적용하여 보았습니다. typedef문이 이곳에서는 어떻게 적용되고 있는지 살펴보도록 합시다.


typedef void (*ptrfunc)(int);


 보시는 대로 기존에 있던 함수 포인터가 선언된 부분 앞쪽에 typedef가 선언되어 있는 모습을 보고 계십니다. typedef문은 빈번하게 사용되는 소스코드가 복잡하거나 길 경우 이를 간결하게 사용하기 위한 목적으로 사용되는데요 함수 포인터에서의 typedef문은 지금껏 보았던 typedef문과는 약간 사용되는 방법이 다르지만 결국은 사용되는 목적은 같습니다.


 다음으로 typedef 함수 포인터가 응용되는 부분을 보여드리도록 하겠습니다.


    ptrfunc elecs;
    elecs = testprint;


 응용이라고 말씀드려서 뭔가 거창한 걸 하려나 하겠습니다만 사실 typedef로 선언된 함수 포인터는 위에서 보시는 바와 같이 매우 간결하게 쓰이고 있음을 아실 수 있습니다. ptrfunc로 정의된 typedef문의 함수 포인터를 elecs라는 이름의 함수 포인터 하나를 만들었다고 보시면 됩니다. 쉽게 설명해서 함수포인터인 변수 하나가 생겼다고 생각하시면 됩니다. 아직도 이해가 안되신다면 아래의 간단한 소스코드를 보시면 아하! 하고 이해하실 겁니다.


int elecs;

elecs = 199;


 이제 감이 오시는지요? 그렇습니다! typedef문으로 선언된 함수 포인터는 마치 자료형을 선언하는 것과 같이 간단하게 함수 포인터 변수를 선언한다고 생각하시면 되는 것입니다! 혹시나 해서 아직도 이해하지 못하신 분들을 위해 저 위에 typedef 함수 포인터가 실제로는 어떻게 구현되어 있는지 보여드리겠습니다.


    void (*elecs)(int);
    elecs = testprint;


 위에서 보시는 바와 같이 ptrfunc 부분이 elecs로 치환된 것이라고 생각하시면 제 설명을 정확히 이해하시는 것입니다!


 혹시 typedef 함수 포인터의 원리에 대해 알고자 하셔서 오신 분들이라면 포스팅을 여기까지만 읽어주셔도 자신의 실력으로 함수 포인터를 활용하실 수 있으리라 생각합니다. 아래에서 부터는 다소 어려우니 기죽지 마시고 이렇게 활용되고 있구나 하는 생각으로 읽어주셨으면 합니다.


 그렇다면 이제 실전에서 사용되고 있는 코드를 보도록 하겠습니다. 아래의 소스코드는 안드로이드 내에서 구현된 함수 포인터 입니다. 언어는 C++로 구성되어 있습니다만 함수포인터를 설멍하는데 큰 어려움은 없을 것입니다. 소스는 다음과 같습니다.


/frameworks/av/camera/CameraBase.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template <typename TCam, typename TCamTraits>
sp<TCam> CameraBase<TCam, TCamTraits>::connect(int cameraId,
                                               const String16& clientPackageName,
                                               int clientUid)
{
    ALOGV("%s: connect", __FUNCTION__);
    sp<TCam> c = new TCam(cameraId);
    sp<TCamCallbacks> cl = c;
    status_t status = NO_ERROR;
    const sp<ICameraService>& cs = getCameraService();
 
    if (cs != 0) {
        TCamConnectService fnConnectService = TCamTraits::fnConnectService;
        status = (cs.get()->*fnConnectService)(cl, cameraId, clientPackageName, clientUid,
                                             /*out*/ c->mCamera);
    }
    if (status == OK && c->mCamera != 0) {
        c->mCamera->asBinder()->linkToDeath(c);
        c->mStatus = NO_ERROR;
    } else {
        ALOGW("An error occurred while connecting to camera: %d", cameraId);
        c.clear();
    }
    return c;
}
cs


 여기서 참으로 특이한 구조의 소스코드를 만나게 되었습니다.

TCamConnectService fnConnectService = TCamTraits::fnConnectService;


이제 이 부분이 어떻게 구현되었는지 자세히 보도록 합시다.

/frameworks/av/include/camera/CameraBase.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <typename TCam>
struct CameraTraits {
};
 
template <typename TCam, typename TCamTraits = CameraTraits<TCam> >
class CameraBase : public IBinder::DeathRecipient
{
public:
    typedef typename TCamTraits::TCamListener       TCamListener;
    typedef typename TCamTraits::TCamUser           TCamUser;
    typedef typename TCamTraits::TCamCallbacks      TCamCallbacks;
    typedef typename TCamTraits::TCamConnectService TCamConnectService;
....
}
cs

위 코드를 통하여 다음과 같은 사실을 알아내었습니다.

    typedef typename TCamTraits::TCamConnectService TCamConnectService;


 TCamConnectService로 정의된 부분이 CameraTraits<TCam>::TCamConnectService와 동일하다는 것을 알고 다음으로 CameraTraits에 대해 확인해 보도록 하겠습니다.


/frameworks/av/include/camera/Camera.h

1
2
3
4
5
6
7
8
9
10
11
12
template <>
struct CameraTraits<Camera>
{
    typedef CameraListener        TCamListener;
    typedef ICamera               TCamUser;
    typedef ICameraClient         TCamCallbacks;
    typedef status_t (ICameraService::*TCamConnectService)(const sp<ICameraClient>&,
                                                           intconst String16&, int,
                                                           /*out*/
                                                           sp<ICamera>&);
    static TCamConnectService     fnConnectService;
};
cs


위 코드에서 정말 요상하게 친구가 하나 보이는군요.

typedef status_t (ICameraService::*TCamConnectService)(const sp<ICameraClient>&,

                                                           intconst String16&, int,
                                                           /*out*/
                                                           sp<ICamera>&);


 위에서 배운 바와 같이 해당 코드는 typedef 함수 포인터입니다. 다만 포인터 함수의 이름이 참으로 독특한데 이는 멤버 포인터라는 C++에서 사용되고 있는 기능입니다. 멤버 포인터에 대해 좀 더 자세히 알고 싶으신 분은 아래 포스팅을 참조해 주시기 바랍니다.

http://showmiso.tistory.com/210


바로 그 아래에는 typedef로 선언된 함수 포인터에 대한 변수를 static으로 선언되었음을 확인하실 수 있습니다.


 static TCamConnectService     fnConnectService;


 이제 여기서 다시 앞에서 확인하였던 선언문을 다시 한 번 보도록 합니다.


TCamConnectService fnConnectService = TCamTraits::fnConnectService;


 위 소스코드는 TCamConnectService로 선언된 typedef 함수 포인터를 가진 변수명 fnConnectService 안에 TCamTratis::fnConnectService 함수의 주소값을 넣겠다는 의미로 이해해 주시면 되겠습니다. 그렇다면 여기서 TCamTratis::fnConnectService 함수는 어떻게 구현되었는지 찾아보도록 하겠습니다.


/frameworks/av/camera/Camera.cpp

1
2
CameraTraits<Camera>::TCamConnectService CameraTraits<Camera>::fnConnectService =
        &ICameraService::connect;
cs


 Camera.cpp 소스 코드 내에서 'CameraTraits<Camera>::fnConnectService' 라는 이름의 포인터 함수 변수가 선언되었고 해당 포인터 함수에 ICameraService::connect 함수의 주소를 넣어준다고 이해하시면 되겠습니다.


/frameworks/av/include/camera/ICameraService.h

1
2
3
4
5
6
7
8
9
10
11
12
class ICameraService : public IInterface
{
public:
....
    virtual status_t connect(const sp<ICameraClient>& cameraClient,
            int cameraId,
            const String16& clientPackageName,
            int clientUid,
            /*out*/
            sp<ICamera>& device) = 0;
....
}
cs


/frameworks/av/camera/ICameraService.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class BpCameraService: public BpInterface<ICameraService>
{
public:
    BpCameraService(const sp<IBinder>& impl)
        : BpInterface<ICameraService>(impl)
    {
    }
....
    // connect to camera service (android.hardware.Camera)
    virtual status_t connect(const sp<ICameraClient>& cameraClient, int cameraId,
                             const String16 &clientPackageName, int clientUid,
                             /*out*/
                             sp<ICamera>& device)
    {
        Parcel data, reply;
        data.writeInterfaceToken(ICameraService::getInterfaceDescriptor());
        data.writeStrongBinder(cameraClient->asBinder());
        data.writeInt32(cameraId);
        data.writeString16(clientPackageName);
        data.writeInt32(clientUid);
        remote()->transact(BnCameraService::CONNECT, data, &reply);
 
        if (readExceptionCode(reply)) return -EPROTO;
        status_t status = reply.readInt32();
        if (reply.readInt32() != 0) {
            device = interface_cast<ICamera>(reply.readStrongBinder());
        }
        return status;
    }
....
}
cs

 드디어 우리는 connect 함수를 찾아내는 데 성공하였습니다. 이로서 함수 포인터가 정의 되는 과정을 모두 살펴보았습니다. 마지막으로 이 기나긴 여정을 코드로 간결하게 요약하자면 다음과 같습니다.


TCamConnectService fnConnectService = TCamTraits::fnConnectService;


위 코드는 아래와 같이 변동이 됨을 확인하실 수 있습니다.


status_t (ICameraService::*TCamConnectService)(const sp<ICameraClient>&,

                                                           intconst String16&, int,
                                                           /*out*/
                                                           sp<ICamera>&);

TCamConnectService =
&ICameraService::connect;



 위의 코드를 처음 보시는 분들은 이 시점에서도 모두 이해가 안 되실 수 있습니다. 하지만 위 코드에서 일정 부분 만이라도 이해하셨다면 여러분들은 성공하신 겁니다!


  • 노태규 2016.03.02 00:45 ADDR 수정/삭제 답글

    마지막에 ICameraService 에 있는 connect 함수에서 BpCameraService connect 함수로 넘어 가는부분이 이해가 잘안되네요....

    • Justin T. 2016.03.04 01:23 신고 수정/삭제

      아래 포스팅을 통해 자세히 설명드렸습니다.
      http://elecs.tistory.com/173

[JAVA]윈도 CMD를 통해 자바 Command Line 명령어 활용하기

프로그래밍 팁 2014.10.04 01:54

 자바로 프로그래밍을 하시는 분들이라면 많은 분들께서 eclipse를 통해 프로그램을 컴파일 하고 실행하실 겁니다. 아시는 분들이라면 아시겠지만 사실 eclipse는 자바를 좀 더 편하게 작업할 수 있게 해주는 IDE(통합 개발 환경)입니다. 자바를 처음 접하는 분들에게 마치 eclipse 자체가 자바인 것처럼 이해하셨던 분들도 많으셨으리라 생각합니다.

 그렇다면 아마 어떤 분들은 '그렇다면 eclipse를 사용하지 않고도 자바 프로그래밍이 가능하다는 건가?'라는 생각을 하시는 분도 계실겁니다. 정답은 '그렇다'입니다! 그것도 Windows 의 CMD(명령 프롬프트)만으로도 컴파일 및 실행이 된다는 것이지요!

 이번 포스팅에서는 윈도의 CMD를 활용하여 자바 프로그램을 간단하게 만들어보도록 하겠습니다.


※본 포스팅을 읽기 전에 자신의 컴퓨터에 자바 JDK가 설치되어있는지 확인합니다. 만약 설치가 되어있지 않다면 최신 버전의 JAVA JDK를 설치하도록 합니다.


http://www.oracle.com/technetwork/java/javase/downloads/index.html



 위의 사진과 같이 JAVA SE 다운로드 페이지가 나오면 왼쪽의 Java Platform (JDK) 를 선택 후 자신의 운영체제에 맞는 버전을 설치하면 된다.


1. 자신의 컴퓨터에 JDK가 설치되었다면 환경변수를 변경하에 JAVA 컴파일을 할 수 있도록 설정해줍니다. 먼저 자신의 컴퓨터에 JDK가 설치된 폴더로 이동한 후 bin 폴더 내의 javac가 있는 것을 확인합니다.



 JDK가 정상적으로 설치되었다면 다음과 같이 javac 파일을 확인하실 수 있습니다. JDK에서 기본설정된 폴더대로 설치하셨다면 C:\Program Files\Java\자신의 컴퓨터에 깔린 JDK의 버전(jdkX.X.X_XX)\bin


2.창의 윗부분을 오른쪽 클릭을 한 후 '주소 복사'를 클릭힙니다.



3. 컴퓨터에서 마우스 우측 클릭후 '속성'을 클릭합니다.



4. '고급 시스템 설정'을 클릭합니다.



5. 시스템 속성에서 '고급' 탭을 선택한 후 '환경 변수'를 클릭합니다.



6. 환경변수 창에서 시스템변수(S) 내에 Path를 더블클릭합니다.



7. 변수 값의 뒷부분에 다음 값을 추가합니다.

위의 과정에서 복사한 주소를 Ctrl+v로 붙여넣기 후 '\bin;'를 붙여줍니다.



※주의!!

 절대로 변수 값의 내용을 지우시면 안됩니다! 만약 실수로 변수 값을 덮어쓰셨다면 바로 취소 버튼을 누릅니다. 만약 기존의 환경변수를 덮어쓰기로 모두 삭제할 경우 연결된 프로그램들이 동작을 하지 못하게 되는 참사가 발생합니다!

 만약 실수로 덮어쓰기 후 확인 버튼을 눌러 변수가 지워지신 분들은 절대로 당황하지 마시고 제가 이전에 포스팅한 내용을 따라가며 원래 환경 변수값을 복구하시길 바랍니다.


[윈도7] 환경변수 path 삭제시 복구하는 방법 ← 클릭하시면 새창으로 이동합니다.


========================================================================================================

 여기까지 진행하셨다면 Windows의 CMD를 통한 자바 프로그래밍의 설정을 완료하였습니다. 이제부터 본격적으로 CMD를 통해 자바 프로그래밍을 진행해 보도록 하겠습니다.


8. 메모장을 열고 코드를 간단하게 작성합니다.



9. 작성한 코드를 .java 확장명으로 저장합니다.

여기서 주의할 점은 파일명은 반드시 public로 설정된 class의 명칭으로 하여야 합니다!



10. dir 명령어를 입력하여 java 파일이 생성된 것을 확인합니다.



11. 명령어를 입력하여 java 파일을 컴파일 합니다. 컴파일 명령어는 다음과 같이 입력합니다.


> javac 파일명.java



컴파일이 완료되면 위의 그림에서 보는 바와 같이 class 파일이 생성된 것을 확인할 수 있습니다.


12. 생성된 class 파일을 실행합니다. 실행 명령어는 다음과 같이 입력합니다.


> java 클래스명



class 파일을 실행하면 프로그램일 정상적으로 실행되고 있는 것을 확인할 수 있다.

다음은 커맨드 라인으로 입력된 값을 그대로 출력하는 프로그램을 작성해 보겠습니다.


13. Command Line을 통해 입력된 값들을 처리하는 부분을 추가합니다.

Command Line에 추가로 입력된 값들은 main 함수의 인자인 String 배열 변수인 args를 통해 프로그램에 적용됩니다.


14. 위에서 진행했던 과정대로 컴파일한 후 프로그램을 실행하면 다음과 같은 결과를 얻을 수 있습니다.



위에서 입력된 값은 다음과 같은 구성을 하고 있습니다.


>java 클래스명 args[0] args[1] args[2] .....


이와 같이 입력된 String 값들이 main의 args 인자로 출력할 수 있게 됨을 확인하실 수 있습니다.

[JAVA] 간단한 파일 입출력(FILE I/O) 구현

프로그래밍 팁 2014.09.10 01:13

 자바로 프로그래밍을 하시는 분들이라면 가끔 파일 입출력(File I/O)를 써 줘야할 때가 간혹 발생할 겁니다.

평소엔 잘 쓰지도 않던 것인데 갑작스럽게 사용하게 되면 사용법이 헷갈려서 헤매는 경우도 많지요.


 아래는 JAVA 환경에서 간단하게 구현해 본 파일 입출력입니다. 프로그램은 먼저 FileOutputStream을 통해 파일을 기록한 후 해당 파일을 다시 FileInputStream을 통해 읽어 들이는 구조로 되어 있습니다.


※ 파일 입출력을 위해 사용된 PrintStream 클래스와 BufferedReader 클래스의 경우 새로 선언되더라도 가비지 컬렉터로 바로 사라지지 않습니다. 해당 클래스를 모두 사용한 후 반드시 close() 함수를 통해 해당 스트림을 종료하셔야 합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static void main(String[] args) {
        // TODO Auto-generated method stub
        File file;
        
        try {
            file = new File("file.txt");
            
            PrintStream ps = new PrintStream(new FileOutputStream(file));
            PrintStream stdout = System.out;
            System.setOut(ps);
            System.out.print("Hello, World!\n http://elecs.tistory.com/");
            ps.close();
            
            BufferedReader br = new BufferedReader(new FileReader(file));
            System.setOut(stdout);
            String str;
            while(true){
                str = br.readLine();
                if(str==null)
                    break;
                System.out.println(str);
            }
            br.close();
            
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }




프로그램을 실행하면 다음과 같이 프로젝트 폴더 내에 file.txt 파일이 생성된 것을 확인하실 수 있습니다.



생성된 파일을 다시 읽어 다음과 같이 출력하실 수 있습니다.