'프로그래밍/C++'에 해당되는 글 9건

  1. 2015.09.10 파일 로깅
  2. 2015.09.04 c++11 pair<> class
  3. 2015.08.11 secure socket programming
  4. 2013.12.02 winsock broadcast 예제 소스
  5. 2013.11.28 c++ unsigned 변수
  6. 2013.11.05 element size of array
  7. 2013.10.23 상속 관계에서 access modifier 2
  8. 2013.10.17 객체를 반환하는 함수
  9. 2013.10.14 배열과 포인터

파일 로깅

프로그래밍/C++ 2015. 9. 10. 17:07 |

테스트 할 때 로그를 출력하기 거시기 할 때 그냥 파일에 쓰게 간단히 코드를 만들어 둠.

 

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
32
#ifndef _BABO_LOG_H
#define _BABO_LOG_H
 
#include <cstdio>
#include <cstdarg>
#include <fstream>
 
using namespace std;
 
class Log {
private:
    Log() { file.open("/var/log/log.txt", ios::out | ios::app); } // 파일에 계속 이어 붙인다.
    ~Log() { file.close(); }
    ofstream file;
public:
    static Log &instance() { // singleton
        static Log _instance;
        return _instance;
    }
    void log(const char *format, ...) {
        char buffer[256];
        va_list args;
        va_start(args, format);
        vsnprintf(buffer, sizeof(buffer), format, args);
        va_end(args);
        file << buffer;
    }
};
 
#define log(format, args...) Log::instance().log(format, ##args)
 
#endif // _BABO_LOG_H

 

 

다음과 같이 사용하면 된다.

1
2
3
4
5
6
7
#include "Log.h"
 
int main() {
    for (int i = 0; i < 10; i++)
        log("%d\n", i);
    return 0;
}


:

pair<> 클래스

 

pair 는 c++98 에도 이미 제공되고 있었다.

pair 는 두 객체를 하나의 타입으로 묶어 사용한다.

예를들면, 2차원 평면 상에서 좌표를 표현하는 경우 다음과 같이 사용할 수 있다.

pair<intint> 2d_point;

또 map 계열의 클래스는 키와 값의 쌍으로 구성되는데 이때도 pair 를 사용한다.

각 타입은 template 으로 정의 되므로 다른 타입이 사용될 수 있다.

pair 를 선언을 하면 선언 시에 사용된 객체의 default constructor 가 불리며 int 와 같은 primitive 타입이라면 0 으로 초기화 된다.

 

pair 정의 코드

bits/stl_pair.h g++ (GCC) 4.8.2
/**
   *  @brief Struct holding two objects of arbitrary type.
   *
   *  @tparam _T1  Type of first object.
   *  @tparam _T2  Type of second object.
   */
  template<class _T1, class _T2>
    struct pair
    {
      typedef _T1 first_type;    /// @c first_type is the first bound type
      typedef _T2 second_type;   /// @c second_type is the second bound type
      _T1 first;                 /// @c first is a copy of the first object
      _T2 second;                /// @c second is a copy of the second object
      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // 265.  std::pair::pair() effects overly restrictive
      /** The default constructor creates @c first and @c second using their
       *  respective default constructors.  */
      _GLIBCXX_CONSTEXPR pair()
      : first(), second() { }
      /** Two objects may be passed to a @c pair constructor to be copied.  */
      _GLIBCXX_CONSTEXPR pair(const _T1& __a, const _T2& __b)
      : first(__a), second(__b) { }
      /** There is also a templated copy ctor for the @c pair class itself.  */
#if __cplusplus < 201103L
      template<class _U1, class _U2>
    pair(const pair<_U1, _U2>& __p)
    : first(__p.first), second(__p.second) { }
#else
      template<class _U1, class _U2, class typename
           enable_if<__and_<is_convertible<const _U1&, _T1>,
                is_convertible<const _U2&, _T2>>::value>::type>
    constexpr pair(const pair<_U1, _U2>& __p)
    : first(__p.first), second(__p.second) { }
      constexpr pair(const pair&) = default;
      constexpr pair(pair&&) = default;
 
 
      // DR 811.
      template<class _U1, class typename
           enable_if<is_convertible<_U1, _T1>::value>::type>
    constexpr pair(_U1&& __x, const _T2& __y)
    : first(std::forward<_U1>(__x)), second(__y) { }
      template<class _U2, class typename
           enable_if<is_convertible<_U2, _T2>::value>::type>
    constexpr pair(const _T1& __x, _U2&& __y)
    : first(__x), second(std::forward<_U2>(__y)) { }
      template<class _U1, class _U2, class typename
           enable_if<__and_<is_convertible<_U1, _T1>,
                is_convertible<_U2, _T2>>::value>::type>
    constexpr pair(_U1&& __x, _U2&& __y)
    : first(std::forward<_U1>(__x)), second(std::forward<_U2>(__y)) { }
      template<class _U1, class _U2, class typename
           enable_if<__and_<is_convertible<_U1, _T1>,
                is_convertible<_U2, _T2>>::value>::type>
    constexpr pair(pair<_U1, _U2>&& __p)
    : first(std::forward<_U1>(__p.first)),
      second(std::forward<_U2>(__p.second)) { }
      template<typename... _Args1, typename... _Args2>
        pair(piecewise_construct_t, tuple<_Args1...>, tuple<_Args2...>);
      pair&
      operator=(const pair& __p)
      {
    first = __p.first;
    second = __p.second;
    return *this;
      }
 
 
      template<class _U1, class _U2>
    pair&
    operator=(const pair<_U1, _U2>& __p)
    {
      first = __p.first;
      second = __p.second;
      return *this;
    }
      template<class _U1, class _U2>
    pair&
    operator=(pair<_U1, _U2>&& __p)
    {
      first = std::forward<_U1>(__p.first);
      second = std::forward<_U2>(__p.second);
      return *this;
    }
      void
      swap(pair& __p)
      noexcept(noexcept(swap(first, __p.first))
           && noexcept(swap(second, __p.second)))
      {
    using std::swap;
    swap(first, __p.first);
    swap(second, __p.second);
      }
    private:
      template<typename... _Args1, std::size_t... _Indexes1,
               typename... _Args2, std::size_t... _Indexes2>
        pair(tuple<_Args1...>&, tuple<_Args2...>&,
             _Index_tuple<_Indexes1...>, _Index_tuple<_Indexes2...>);
#endif
    };

 

pair 는 c++11 에서 크게 확장된 것은 없지만 표준 확장에 따라 move constructor 가 제공되고 swap() 이 추가되었다.

그 외 사용법은 c++98 에 비해 크게 달라진 점은 없다.

멈버 변수인 first 와 second 가 public 으로 선언되어 있기 때문에 접근 제한 없이 사용할 수 있다.

 

pair 를 간단히 생성하기 위해서 make_pair() 라는 함수가 존재한다.

make_pair 는 객체 생성을 위해 따로 타입을 쓰지 않아도 된다.

make_pair
pair<intfloat> int_float(0, 0.0f);

위 코드 대신 아래와 같이 사용할 수 있다.

make_pair
auto int_float = make_pair(0, 0.0f);

 

 

make_pair 의 코드:

bits/stl_pair.h g++ (GCC) 4.8.2
   /**
   *  @brief A convenience wrapper for creating a pair from two objects.
   *  @param  __x  The first object.
   *  @param  __y  The second object.
   *  @return   A newly-constructed pair<> object of the appropriate type.
   *
   *  The standard requires that the objects be passed by reference-to-const,
   *  but LWG issue #181 says they should be passed by const value.  We follow
   *  the LWG by default.
   */
  // _GLIBCXX_RESOLVE_LIB_DEFECTS
  // 181.  make_pair() unintended behavior
#if __cplusplus >= 201103L
  // NB: DR 706.
  template<class _T1, class _T2>
    constexpr pair<typename __decay_and_strip<_T1>::__type,
                   typename __decay_and_strip<_T2>::__type>
    make_pair(_T1&& __x, _T2&& __y)
    {
      typedef typename __decay_and_strip<_T1>::__type __ds_type1;
      typedef typename __decay_and_strip<_T2>::__type __ds_type2;
      typedef pair<__ds_type1, __ds_type2>        __pair_type;
      return __pair_type(std::forward<_T1>(__x), std::forward<_T2>(__y));
    }
#else
  template<class _T1, class _T2>
    inline pair<_T1, _T2>
    make_pair(_T1 __x, _T2 __y)
    return pair<_T1, _T2>(__x, __y); }
#endif

 

c++11 의 make_pair 는 rvalue 인자를 받으므로,

해당 타입이 move semantics 를 지원한다면 인자로 전달된 객체가 변경된다.

 

간단한 테스트 코드 :

example : make_pair
#include <iostream>
#include <vector>
#include <utility>
using namespace std;
 
void print(const vector<int> &v) {
    cout << "[ ";
    for (auto i : v) {
        cout << i << " ";
    }  
    cout << " ]" << endl;
}
 
int main() {
    vector<int> a;
    a.push_back(1);
    a.push_back(2);
 
    print(a);
    vector<int> b;
    b.push_back(10);
    b.push_back(20);
    print(b);
 
    auto c = make_pair(move(a), move(b)); // 이제 a 와 b 를 사용하지 않는다
    print(a);
    print(b);
    return 0;
}
출력 결과
[ 1 2  ]
[ 10 20  ]
[  ]
[  ]


:

secure socket programming

 

server

 Collapse source
//SSL-Server.c
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <malloc.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <resolv.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
  
#define FAIL    -1
  
int OpenListener(int port)
{   int sd;
    struct sockaddr_in addr;
  
    sd = socket(PF_INET, SOCK_STREAM, 0);
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;
    if ( bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0 )
    {
        perror("can't bind port");
        abort();
    }
    if ( listen(sd, 10) != 0 )
    {
        perror("Can't configure listening port");
        abort();
    }
    return sd;
}
  
SSL_CTX* InitServerCTX(void)
{   SSL_METHOD *method;
    SSL_CTX *ctx;
  
    OpenSSL_add_all_algorithms();  /* load & register all cryptos, etc. */
    SSL_load_error_strings();   /* load all error messages */
    method = SSLv23_server_method();  /* create new server-method instance */
    ctx = SSL_CTX_new(method);   /* create new context from method */
    if ( ctx == NULL )
    {
        ERR_print_errors_fp(stderr);
        abort();
    }
    return ctx;
}
  
void LoadCertificates(SSL_CTX* ctx, char* CertFile, char* KeyFile)
{
 /* set the local certificate from CertFile */
    if ( SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0 )
    {
        ERR_print_errors_fp(stderr);
        abort();
    }
    /* set the private key from KeyFile (may be the same as CertFile) */
    if ( SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0 )
    {
        ERR_print_errors_fp(stderr);
        abort();
    }
    /* verify private key */
    if ( !SSL_CTX_check_private_key(ctx) )
    {
        fprintf(stderr, "Private key does not match the public certificate\n");
        abort();
    }
}
  
void ShowCerts(SSL* ssl)
{   X509 *cert;
    char *line;
  
    cert = SSL_get_peer_certificate(ssl); /* Get certificates (if available) */
    if ( cert != NULL )
    {
        printf("Server certificates:\n");
        line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
        printf("Subject: %s\n", line);
        free(line);
        line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
        printf("Issuer: %s\n", line);
        free(line);
        X509_free(cert);
    }
    else
        printf("No certificates.\n");
}
  
void Servlet(SSL* ssl) /* Serve the connection -- threadable */
{   char buf[1024];
    char reply[1024];
    int sd, bytes;
    const char* HTMLecho="<html><body><pre>%s</pre></body></html>\n\n";
  
    if ( SSL_accept(ssl) == FAIL )     /* do SSL-protocol accept */
        ERR_print_errors_fp(stderr);
    else
    {
        ShowCerts(ssl);        /* get any certificates */
        bytes = SSL_read(ssl, buf, sizeof(buf)); /* get request */
        if ( bytes > 0 )
        {
            buf[bytes] = 0;
            printf("Client msg: \"%s\"\n", buf);
            sprintf(reply, HTMLecho, buf);   /* construct reply */
            SSL_write(ssl, reply, strlen(reply)); /* send reply */
        }
        else
            ERR_print_errors_fp(stderr);
    }
    sd = SSL_get_fd(ssl);       /* get socket connection */
    SSL_free(ssl);         /* release SSL state */
    close(sd);          /* close connection */
}
  
int main(int count, char *strings[])
{   SSL_CTX *ctx;
    int server;
    char *portnum;
  
    if ( count != 2 )
    {
        printf("Usage: %s <portnum>\n", strings[0]);
        exit(0);
    }
    SSL_library_init();
  
    portnum = strings[1];
    ctx = InitServerCTX();        /* initialize SSL */
    LoadCertificates(ctx, "mycert.pem""mycert.pem"); /* load certs */
    server = OpenListener(atoi(portnum));    /* create server socket */
    while (1)
    {   struct sockaddr_in addr;
        socklen_t len = sizeof(addr);
        SSL *ssl;
  
        int client = accept(server, (struct sockaddr*)&addr, &len);  /* accept connection as usual */
        printf("Connection: %s:%d\n",inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
        ssl = SSL_new(ctx);              /* get new SSL state with context */
        SSL_set_fd(ssl, client);      /* set connection socket to SSL state */
        Servlet(ssl);         /* service connection */
    }
    close(server);          /* close server socket */
    SSL_CTX_free(ctx);         /* release context */
    return 0;
}

컴파일 :

gcc -Wall -o server server.c -lssl -lcrypto

 

 

 

 

client

 Collapse source
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <malloc.h>
#include <string.h>
#include <sys/socket.h>
#include <resolv.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
  
#define FAIL    -1
  
int OpenConnection(const char *hostname, int port)
{   int sd;
    struct hostent *host;
    struct sockaddr_in addr;
  
    if ( (host = gethostbyname(hostname)) == NULL )
    {
        perror(hostname);
        abort();
    }
    sd = socket(PF_INET, SOCK_STREAM, 0);
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = *(long*)(host->h_addr);
    if ( connect(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0 )
    {
        close(sd);
        perror(hostname);
        abort();
    }
    return sd;
}
  
SSL_CTX* InitCTX(void)
{   SSL_METHOD *method;
    SSL_CTX *ctx;
  
    OpenSSL_add_all_algorithms();  /* Load cryptos, et.al. */
    SSL_load_error_strings();   /* Bring in and register error messages */
    method = SSLv23_client_method();  /* Create new client-method instance */
    ctx = SSL_CTX_new(method);   /* Create new context */
    if ( ctx == NULL )
    {
        ERR_print_errors_fp(stderr);
        abort();
    }
    return ctx;
}
  
void ShowCerts(SSL* ssl)
{   X509 *cert;
    char *line;
  
    cert = SSL_get_peer_certificate(ssl); /* get the server's certificate */
    if ( cert != NULL )
    {
        printf("Server certificates:\n");
        line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
        printf("Subject: %s\n", line);
        free(line);       /* free the malloc'ed string */
        line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
        printf("Issuer: %s\n", line);
        free(line);       /* free the malloc'ed string */
        X509_free(cert);     /* free the malloc'ed certificate copy */
    }
    else
        printf("No certificates.\n");
}
  
int main(int count, char *strings[])
{   SSL_CTX *ctx;
    int server;
    SSL *ssl;
    char buf[1024];
    int bytes;
    char *hostname, *portnum;
  
    if ( count != 3 )
    {
        printf("usage: %s <hostname> <portnum>\n", strings[0]);
        exit(0);
    }
    SSL_library_init();
 hostname=strings[1];
 portnum=strings[2];
  
    ctx = InitCTX();
    server = OpenConnection(hostname, atoi(portnum));
    ssl = SSL_new(ctx);      /* create new SSL connection state */
    SSL_set_fd(ssl, server);    /* attach the socket descriptor */
    if ( SSL_connect(ssl) == FAIL )   /* perform the connection */
        ERR_print_errors_fp(stderr);
    else
    {   char *msg = "Hello???";
  
        printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
        ShowCerts(ssl);        /* get any certs */
        SSL_write(ssl, msg, strlen(msg));   /* encrypt & send message */
        bytes = SSL_read(ssl, buf, sizeof(buf)); /* get reply & decrypt */
        buf[bytes] = 0;
        printf("Received: \"%s\"\n", buf);
        SSL_free(ssl);        /* release connection state */
    }
    close(server);         /* close socket */
    SSL_CTX_free(ctx);        /* release context */
    return 0;
}
 

컴파일 :

gcc -Wall -o server client.c -lssl -lcrypto

references:

[1] Secure programming with the OpenSSL API, Part 1: Overview of the API : http://www.ibm.com/developerworks/library/l-openssl.html

[2] Korean Version of [1] : http://hasu0707.tistory.com/153

[3] Secure Server Client using OpenSSL in C : http://simplestcodings.blogspot.kr/2010/08/secure-server-client-using-openssl-in-c.html

[4] HTTPS와 SSL 인증서 : http://opentutorials.org/course/228/4894

[5] SSL/TLS Programming : http://www.cs.odu.edu/~cs772/fall08/lectures/ssl_programming.pdf

:

대략 winsock 으로 braodcast 하는 c++ 소스입니다.





아래는 테스트용 reader 코드


references : 

http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNo=20&no=7103&ref=7103
http://blog.daum.net/jjiyong/12873890


:

c/c++ 에서 unsigned 변수를 사용할 땐 주의해야 합니다.

수식에서 signed 변수와 unsigned 변수를 섞어 사용하면 타입 변환이 일어납니다.

타입 변환 규칙을 분명히 알고 사용해야 합니다.

(어렵지는 않습니다. 다른 타입을 가지는 변수들의 연산 결과는 더 넓은 표현 범위의 타입을 가집니다.

고로 signed int 와 unsigned int 의 연산 결과는 unsigned int 입니다.

결과는,

4294967295

4294967295

-1 

입니다.


또한 unsigned 변수를 뺄셈에 사용하면 역시 버그가 발생할 가능성이 생깁니다.

결과가 음수가 나오면 overflow 가 발생하여 예상치 못한 결과가 발생할 수 있습니다.

논리적으로 음수가 절대 발생하지 않는 경우 unsigned 변수를 사용하는 것은 장점이 됩니다.

변수가 0 보다 작은 지를 체크하지 않아도 되기 때문이죠. 예를 들면


대략 이런 식입니다.
array 의 boundary 체크가 빠졌다고 ? 물론 두 가지 함수에 모두 존재해야하므로 생략했습니다.


하지만 좋은 방법은 아니지만, 의도한 경우 이렇게 사용할 수는 있습니다.


약간의 장점이 있지만, 조심해서 사용해야합니다.

제 생각엔 몇 가지 경우를 제외하면 가능한 unsigned 변수를 사용할 필요가 없다고 생각합니다.

대략 count, size 를 표현해야 하는 경우에만 사용하면 되지 않을까 생각합니다.

java 에는 unsigned 변수 타입이 없습니다. (굳이 따지면 char 는 unsigned 타입이긴 합니다만)

왜냐하면 약간의 장점에 비해 복잡하기 때문입니다.

:

array 를 만들 때 크기는 상수로 주어야 한다. 그 이유는 실행 전에 메모리 할당 크기를 알아야 하기 때문이다.

대략, 다음과 같이

int array [10] = {0,};


NOTE: The elements field within brackets [] which represents the number of elements the array is going to hold, must be a constant value, since arrays are blocks of non-dynamic memory whose size must be determined before execution. In order to create arrays with a variable length dynamic memory is needed, which is explained later in these tutorials. [1]


그런데, 요상하게도 gcc 3.4.4 와 4.6.3 에서는 아래 코드가 잘 동작한다.

(비주얼 스튜디오 2010 에서는 바로 9 라인에서 컴파일 에러를 뱉어낸다.)


결과는 대략, (k 에 8을 입력하면)

i : 0x22ac2c

k : 0x22ac28

array : 0x22abd0  // k 와 88 차이 (실제 데이터는 64 바이트)

array2 : 0x22aba0  // array 와 48 차이 (실제 데이터는 32 바이트)

--------------

i : 0x22ac2c

k : 0x22ac28

array : 0x22abe0  // k 와 72 차이 (실제 데이터는 64 바이트)

array2 : 0x22abc0  // array 와 32 차이 (실제 데이터는 32 바이트. 이건 일치!)


원래 예상대로라면 k 주소와 array 주소의 값 차이는 64(4바이트 * 16)가 되어야 하지만,

첫 번째 케이스(func)에서는 88 이고,

두 번째(func2)에서는 72 이다.

각각 24바이트와 8바이트가 비는데, 그곳엔 과연 무엇이 존재하는 걸까 ㅡㅡ;

(시간 나면 더 알아보겠음)



비주얼 스튜디오 2010 에서 func2 만 호출해 본 결과는 아래와 같다.

i : 0018F668

k : 0018F65C

array : 0018F614  // k 와 72 차이 (실제 데이터는 64 바이트)

array2 : 0018F5EC  // k 와 40 차이 (실제 데이터는 32 바이트)





references : 

[1] Arrays, http://www.cplusplus.com/doc/tutorial/arrays/

:

부모 클래스에서 public 이고 자식이 이를 상속받아 private 으로 선언하면 어찌 될까 ?

대략 아래 코드와 같이, 부모의 탈을 쓴 자식 인스턴스를 만들면 멤버함수가 호출이 가능하다.


출력 결과 :

A print

:

객체를 반환하는 함수에서 무슨 일이 일어나는가 ?


$ gcc --version

gcc (GCC) 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)

Copyright (C) 2004 Free Software Foundation, Inc.

This is free software; see the source for copying conditions.  There is NO

warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


일단 아래와 같이 시험용 클래스를 만든다.


객체를 선언한 후, 다음에 = 을 통해 값을 받으면,

출력 결과는 다음과 같다.

default constructor

default constructor

operator=

destructor

destructor


대략 2 라인에서 기본 생성자로 객체 생성

3라인의 getAAA() 내부에서 기본 생성자로 객체 생성 (임시 객체 생성)

3라인의 = 에서 대입연산자 호출

그리고 즉시 임시 객체 소멸

4라인에서 a 소멸



다음과 같이 이 두 줄의 코드를 한 줄로 변경하면,

default constructor

destructor


요렇게 된다.
이렇게 되는 이유는 대략 return value optimization. 아래 링크 참조

그런데 아무런 옵션이 없어도 저절로 이렇게 된다. 요즘 컴파일러는 똑똑하다.


레퍼런스 타입으로 함수를 호출하면 역시 동일하다.

default constructor

0 0

destructor



call by value 로 부르면,

default constructor

0 0

destructor



함수의 파라미터 타입이 const 여도, 그렇지 않아도 같은 결과가 나온다 @.@


[+]
c++11 에서는 move constructor 를 이용할 수 있다.


:

배열과 포인터


배열과 포인터는 거의 유사하게 사용된다.

차이점은, 배열은 값이 변하지 않는 const 속성을 갖는다는 점이다.

즉, 포인터는 주소값을 할당할 수 있지만 어레이 변수는 그게 안 된다.



포인터에 어레이 변수를 할당하면 어레이 변수와 동일하게 사용할 수 있다.


한편, [] 오퍼레이터는 계산은 다음과 같은데,
a[i] == *(a+i)

그래서 아래 두 문장은 서로 같은 값을 출력한다.


: