반응형

Jenkins 서비스를 구동시킨 후 Jenkins 웹 페이지로 접속한다.

Jenkins 웹 페이지에 처음 접속하면, 아래와 같이 초기 관리자 비밀번호를 입력하라는 페이지가 출력된다.

 

초기 관리자 비밀번호 파일 내용을 복사하여 붙여넣기 한 후 [Continue] 버튼을 누른다.

$ cat /var/lib/jenkins/secrets/initialAdminPassword

 

이번엔 플러그인 설치 화면이 출력된다.

특별히 설치해야 하는 플러그인이 없다면, [Install Suggested plugins]를 선택한다.

 

플러그인 설치가 진행된다. (대략 5~15분 정도 소요됨)

 

플러그인 설치가 완료되면, 관리자 계정 생성 페이지가 출력된다.

계정명, 암호, 이름, 이메일 주소를 입력한 후 저장한다.

 

마지막으로 Jenkins 접속 정보를 수정할 수 있는 페이지가 출력된다.

변경할 내용이 있다면 수정한 후 저장하고 마무리한다.

 

이제 Jenkins를 사용할 수 있게 되었다!!!



 
 
 

 

 

반응형
반응형

Java 17 버전이 설치되었으면, 이제 Jenkins를 설치하여 사용할 수 있다.

 

 

1. Jenkins 저장소 설치

Jenkins를 설치하기 전에 yum repository를 먼저 설치한다.

$ yum -y install ca-certificates
$ wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
$ rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key

 

 

2. Jenkins 설치

$ yum -y install epel-release
$ yum -y install jenkins

 

 

3. Jenkins 설치 확인

$ rpm -qa | grep jenkins
$ systemctl status jenkins

 

 

4. Jenkins 서비스 포트 변경

Jenkins는 기본 포트로 8080 포트를 사용한다. (기본 포트를 사용하려면 이 부분은 skip !!!)

다른 포트를 사용하도록 변경하기 위해서는 Jenkins 설정 파일을 수정한 후 서비스를 재시작해준다.

$ vi /usr/lib/systemd/system/jenkins.service

Environment="JENKINS_PORT=[변경하려는 포트 번호]"

 

만약, 방화벽 서비스(firewalld)를 사용중이라면 변경할 포트를 열어줘야 한다.

$ firewall-cmd --permanent --zone=public --add-port=[변경하려는 포트 번호]/tcp

### firewalld 재시작
$ firewall-cmd --reload

 

 

5. Jenkins 작업 디렉토리(Workspace) 변경

Jenkins의 기본 작업 디렉토리는 "/var/lib/jenkins" 로 설정되어있다. (기본 작업 디렉토리를 사용하려면 이 부분은 skip !!!)

다른 디렉토리를 사용하기 위해서는 Jenkins 설정 파일을 수정한 후 서비스를 재시작해준다.

$ vi /usr/lib/systemd/system/jenkins.service

Environment="JENKINS_HOME=[변경하려는 작업 디렉토리]"
WorkingDirectory=[변경하려는 작업 디렉토리]

 

 

6. Jenkins 구동

$ systemctl daemon-reload
$ systemctl enable --now jenkins

 

 

7. Jenkins 구동 및 포트 확인

$ systemctl status jenkins
$ netstat -anp | grep `ps -ef | grep -v grep | grep jenkins | awk '{print $2}'`

 
 
 
 

 

 

 

반응형
반응형

Jenkins를 사용하기 위해서는 Java가 설치되어 있어야한다.

Jenkins에서는 Java 17 버전 이상 사용을 권장하므로 Java 17 버전을 설치해보자. 

 

참고로 Java 11 버전이 설치된 시스템에 Jenkins를 설치하여 사용하면 아래와 같은 경고를 출력한다.

 

 

1. 필요 패키지 설치

Java 17을 다운로드 받기 위해 wget을 설치한다.

$ yum -y install wget

 

 

2. Java 17 다운로드 및 설치

$ wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.rpm
$ rpm -ivh jdk-17_linux-x64_bin.rpm

 

 

3. Java 버전 확인

$ java --version

 

 

4. Java 활성화 버전 확인

Java 17 버전이 선택되어 있지 않으면, 선택하여 활성화 해준다.

$ alternatives --config java



 
 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

가상 서버에 Jenkins를 설치하여 자동 빌드 환경을 구성하는 방법을 알아보자.

물론, Jenkins Docker 이미지를 사용하면 설치 과정없이 보다 쉽게 자동 빌드 환경을 구성할 수 있다.

하지만, 서버 하나에 [Jenkins 빌드 환경] 과 [Compile 환경] 을 모두 구성해야 한다면, Jenkins를 직접 설치하여 사용해야 할 경우가 생긴다. (특히, C나 C++같이 컴파일이 필요하고, 빌드 환경의 영향을 많이 받는 프로그램을 빌드할 경우에는 더욱 그렇다.)

 

이번 글에서는 Jenkins 빌드 환경을 구성하기 위한 첫 단계로 Linux OS를 설치해보자.

 

1. Linux 설치

Virtual Box에 VM을 하나 생성하고, CentOS 7.9 Minimal 이미지로 부팅을 한다.

부팅 후 언어 선택 화면이 나오면 설치 언어를 선택한다. (영어로 선택해도 무방)

 

언어를 선택했으면, 아래의 항목들을 목적에 맞게 선택한다.

- 날짜와 시간 : 아시아/서울

- 소프트웨어 선택 : 최소 설치

- 설치 대상 : 자동 설정 혹은 수동으로 파티션 설정

 

항목들을 선택했으면, [설치 시작]을 누른다.

 

설치가 진행되는 동안 ROOT 암호를 설정한다. (사용자 생성은 필요시 생성)

 

설치가 완료되면, [재부팅] 버튼을 눌러 시스템을 재시작한다.

 

OS가 정상 설치되었으면, 부팅시 아래처럼 OS 선택 화면이 출력된다. (처음 항목을 선택)

 

처음 항목을 선택하여 부팅하면, 로그인 화면이 출력된다.

설치시 설정한 root 사용자 패스워드를 사용하여 로그인한다.

 

 

2. Linux 설정

아래의 설정들은 보안 관련 설정을 해제하는 설정으로, 보안 설정을 사용중이라면 그대로 유지해도 된다.

 

(1) SELinux 사용해제

$ vi /etc/selinux/config

SELINUX=enforcing 으로 되어있는 부분을 SELINUX=disabled 로 변경한 후 저장

 

 

(2) 방화벽 사용해제

$ systemctl disable --now firewalld

 

(3) 네트워크 설정

$ vi /etc/sysconfig/network-scripts/ifcfg-enp0s3

파일의 내용을 수정 (IPADDR, GATEWAY, NETMASK, DNS)


 
 
 

반응형
반응형

std::tuple은 두 개 이상의 변수나 객체를 하나로 묶어 전달 할 때 사용된다.

 

1. 사용법

#include <tuple>

std::tuple< var-type1, ..., var-typeN > var;

 

(1) 인자
  1-1) var-type : 변수나 객체의 타입을 지정

 

 

2. 사용 예제

#include <iostream>
#include <vector>
#include <tuple>

// 이름, 나이, 키
using Type = std::tuple<std::string,int,float>;

void print(const Type& param)
{
    // std::get을 사용하여 값에 접근
    std::cout << "Name:" << std::get<0>(param) << ", Age:" << std::get<1>(param)
        << ", Height:" << std::get<2>(param) << std::endl;
}

int main()
{
    // 선언과 함께 값 추가
    std::tuple<std::string,int,float> t1{"name1", 10, 164.2};
    print(t1);

    // std::make_tuple을 사용하여 값 추가
    Type t2{std::make_tuple<std::string,int,float>("name2", 20, 183.2)};
    print(t2);

    // tuple 사이즈 알아오기
    std::cout << "tuple size: " << std::tuple_size<Type>() << std::endl;

    // auto를 사용하여 값에 접근
    auto& [name, age, height] = t1;
    std::cout << "Name:" << name << ", Age:" << age << ", Height:" << height << std::endl;

    // std::tie를 사용하여 값에 접근
    std::string n{};
    int a{0};
    float h{0.0f};
    std::tie(n, a, h) = t2;
    std::cout << "Name:" << n << ", Age:" << a << ", Height:" << h << std::endl;

    // 벡터와 함께 사용
    std::vector<Type> vTuple;
    vTuple.emplace_back(t1);
    vTuple.emplace_back(t2);
    vTuple.emplace_back(std::make_tuple<std::string,int,float>("name3", 30, 172.2));
    vTuple.emplace_back(Type("name4", 40, 166.7));

    for (const Type& v : vTuple) print(v);
    
    return 0;
}


 
 
 

반응형
반응형

 

std::pair는 두 개의 변수나 객체를 하나로 묶어 전달 할 때 사용된다.

 

1. 사용법

#include <utility>

std::pair< var-type1, var-type2 > var;

 

(1) 인자

  1-1) var-type : 변수나 객체의 타입을 지정

 

2. 사용 예제

#include <iostream>
#include <vector>
#include <utility>
#include <tuple>

using Type = std::pair<int, std::string>;

void print(const Type& param)
{
    std::cout << param.first << " = " << param.second << std::endl;
}

int main()
{
    // 선언과 함께 값 추가
    std::pair<int,std::string> p1{1, "one"};
    // 값에 직접 접근 (first, second)
    std::cout << p1.first << " = " << p1.second << std::endl; // 1 = one

    // std::make_pair를 사용하여 값 추가
    std::pair<int,std::string> p2{std::make_pair<int,std::string>(2, "two")};
    // std::get을 사용하여 값에 접근
    std::cout << std::get<0>(p2) << " = " << std::get<1>(p2) << std::endl; // 2 = two

    // auto를 사용하여 값에 접근
    auto& [num, str] = p1;
    std::cout << num << " = " << str << std::endl; // 1 = one

    // std::tie를 사용하여 값에 접근
    // #include <tuple> 필요
    int n{0};
    std::string s{};
    std::tie(n, s) = p2;
    std::cout << n << " = " << s << std::endl; // 2 = two

    // 벡터와 함께 사용
    std::vector<Type> vPair;
    vPair.emplace_back(p1);
    vPair.emplace_back(p2);
    vPair.emplace_back(std::make_pair<int,std::string>(3, "three"));
    vPair.emplace_back(Type(4, "four"));

    for (const Type& v : vPair) print(v);

    // 값 수정
    p1.first = 11;
    p1.second = "eleven";
    print(p1); // 11 = eleven

    return 0;
}


 
 
 

 

 

 

 

 

반응형
반응형

일반 배열은 길이 정보가 없어서 인자로 넘기려면 길이 정보를 같이 넘겨야 하고, 인덱스를 조금만 벗어나도 Buffer Overflow 와 같은 문제가 발생하므로 안전하지 않다.

C++20 이후에는 모든 연속 객체(배열, 문자열, 벡터, ...)를 안전하게 참조할 수 있도록 해주는 std::span을 사용할 수 있다.

연속 객체를 std::span으로 참조하게 되면 순차 컨테이너에서 제공하는 함수를 사용할 있다. (길이 정보와 반복자를 가짐)

 

1. 사용법

#include <span>

std::span< var-type > var; // 동적 길이를 가지는 span
std::span< var-type, size > var; // 정적 길이를 가지는 span

 

(1) 인자

  1-1) var-type : 변수나 객체의 타입을 지정

  1-2) size : span 사이즈를 지정하여 정적 span으로 사용

 

2. 사용 예제

#include <iostream>
#include <string>
#include <span>

template <typename T>
void printContainer(const std::span<T>& param)
{
    if (param.empty())
    {
        std::cout << "empty." << std::endl;
        return;
    }

    std::cout << "value:";
    for (const auto& v : param)
    {
        std::cout << " " << v;
    }
    std::cout << ", size: " << param.size() << std::endl;
}

int main()
{
    int iArr[5]{1,2,3,4,5};

    // 배열을 정적 span으로 설정
    std::span<int, 5> spArr{iArr};
    for (auto it = spArr.begin(); it != spArr.end(); ++it)
    {
        std::cout << *it << std::endl; // 1 2 3 4 5
    }

    // 배열을 동적 span으로 설정하여 인자로 사용
    printContainer(std::span<int>{iArr}); // value: 1 2 3 4 5, size: 5

    // 문자열을 동적 span으로 설정
    std::string pStr{"abcdefg"};
    std::span<char> spStr{pStr};
    printContainer(spStr); // value: 1 2 3 4 5, size: 5

    // 빈 span을 인자로 사용
    printContainer(std::span<int>{}); // empty.

    return 0;
}


 
 

 

 

 

 

반응형
반응형

bool 변수의 값이 false일 경우에만 특정 작업을 수행하고, true로 변경하는 경우가 빈번하게 발생한다.

기존에는 보통 아래와 같이 코드를 작성했다.

bool isFirst{false};

if (isFirst == false)
{
    // 작업을 수행
    ...
    ...
    isFirst = true;
}

 

C++14 이후에는 std::exchange를 사용하여 간결한 코드로 작성할 수 있으며, bool형 이외의 변수값을 변경할 수도 있다.

 

1. 사용법

#include <utility>

std::exchange( var, new-value )

 

(1) 인자

  1-1) var : 값을 변경할 변수나 객체

  1-2) new-value : 변경할 값

(2) 반환값 : 변경전의 var 값

 

2. 사용 예제

bool isFirst{false};

if (std::exchange(isFirst, true) == false)
{
    // 작업을 수행
    ...
    ...
}

 

3. 사용 예제 (컨테이너에 사용)

#include <iostream>
#include <utility>
#include <vector>

template <typename T>
void printContainer(const T& pContainer)
{
    for (const auto& v : pContainer)
    {
        std::cout << v << " ";
    }
    std::cout << std::endl;
}

int main()
{
    std::vector<int> vec{};

    // vector에 값을 저장
    std::exchange(vec, {1,2,3,4});
    printContainer(vec); // 1 2 3 4

    // vector의 값을 변경
    std::vector<int> oldVec{std::exchange(vec, {5,6,7,8})};
    printContainer(oldVec); // 1 2 3 4
    printContainer(vec); // 5 6 7 8

    return 0;
}

 

4. 사용 예제 (함수에 사용)

#include <iostream>
#include <utility>
#include <functional>

int main()
{
    std::function<void()> fFunc{[](){
        std::cout << "Original Function" << std::endl;
    }};
    fFunc(); // Original Function

    std::function<void()> fOldFunc{std::exchange(fFunc, [](){
        std::cout << "New Function" << std::endl;
    })};
    fFunc(); // New Function
    fOldFunc(); // Original Function
    
    return 0;
}


 

 

반응형
반응형

보통 함수를 실행한 후 결과값은 하나만 반환받을 수 있다.

함수 실행 결과와 결과값이 모두 필요할 경우에는 함수의 인자로 참조나 포인터 변수를 전달해야 했다.

C++17 이후에는 std::optional을 사용해서 함수의 반환값으로 [함수 실행 결과]와 [결과값]을 한번에 전달받을 수 있다.

 

1. 사용법

#include <optional>

std::optional< var-type > var-name;

 

(1) 인자

  1-1) var-type : 함수 실행 결과값의 타입을 지정

 

2. 사용 예제

#include <iostream>
#include <optional>
#include <string>

std::optional<std::string> toUpper(const std::string& pStr)
{
    if (pStr.empty() == true)
    {
        return std::nullopt;
    }

    std::string str{pStr};
    std::transform(str.begin(), str.end(), str.begin(), [](const char c) {
        return std::toupper(c);
    });

    return std::make_optional<std::string>(str); // return str; 로 대체 가능
}

void printResult(const std::optional<std::string>& pRet)
{
    if (pRet == std::nullopt) // (pRet.has_value() == false)로 대체 가능
    {
        std::cout << "failed !!!" << std::endl;
    }
    else
    {
        std::cout << *pRet << std::endl; // pRet.value()로 사용 가능
    }
}

int main()
{
    std::optional<std::string> ret{toUpper("abcdefg")};
    printResult(ret); // ABCDEFG

    ret = toUpper("");
    printResult(ret); // failed !!!

    return 0;
}


 

반응형
반응형

일반 함수는 [반환형], [함수 이름], [인수] 를 입력하여 선언한다. 

하지만 람다 함수는 이름이 없는 익명 함수로써 아래의 형태로 선언하여 사용하며, std::function 과 함께 사용시 재사용도 가능하다. 

(참고 : std::function - 함수 포인터를 대체하는 키워드)

 

1. 사용법

- 람다 함수는 캡처(Capture), 인자(Parameter), 반환형(Return-Type), 바디(Body) 로 구성되어 있다.
- 캡처와 인자는 콤마(,)로 구분하여 여러 개 지정할 수 있다.

[capture] (parameter) -> return-type { body };

 

(1) 캡처(Capture)

- 캡처는 람다 함수에서 사용할 변수나 상수를 참조나 복사의 형태로 찍어오는 것이다.
- 전역 변수를 참조로 캡처할때는 [&], 복사로 캡처할때는 [=]를 사용한다.
- 지역 변수를 참조로 캡처할때는 [&변수명], 복사로 캡처할때는 [=변수명]을 사용한다.

[&]() { return; } // 모든 변수를 참조로 캡처
[=]() { return; } // 모든 변수를 복사로 캡처
[&var1, var2]() { return; } // var1은 참조, var2는 복사로 캡처
[=, &var1]() { return; } // var1만 참조로 캡처하고, 다른 변수는 복사로 캡처
[&, var2]() { return; } // var2만 복사로 캡처하고, 다른 변수는 참조로 캡처

 

- 복사로 캡처된 변수는 기본적으로 constexpr 타입으로 지정되어 값의 변경이 불가하다.

- mutable로 선언하면 값의 변경이 가능하다.

int var1{0};

[var1]() {
    var1 = 999; // 에러!!! - var1은 constexpr 타입이므로 변경이 불가
}();
[var1]() mutable {
    var1 = 999; // var1은 mutable로 선언되어 변경이 가능, 단 실제 값은 변하지 않음
    std::cout << var1 << std::endl; // 999
}();

std::cout << var1 << std::endl; // 0

 

(2) 인자(Parameter)

- 일반 함수의 인자처럼 람다 함수에서 사용할 인자를 지정한다.

[](int a) -> int { return a; } // 1개의 인자를 설정
[](int a, int b) -> int { return (a+b); } // 2개의 인자를 설정


(3) 반환형(Return-Type)

- 람다 함수의 실행 결과를 반환할 반환 타입을 지정한다.


(4) 바디(Body)

- 람다 함수에서 수행할 작업을 기술한다.

 

2. 사용 예제

#include <iostream>
#include <functional>

int main()
{
    // 간단한 람다 함수
    []() {
        std::cout << "Simple Lamda Function" << std::endl;
    }();
    
    // 인자 2개를 더해서 sum에 저장하고, 더한 값을 반환하는 람다 함수
    int sum{0};
    // std::function 에 람다 함수를 정의
    std::function<int(int,int)> fSum{[&sum](int a, int b) -> int {
        sum = a + b;
        return sum;
    }};

    std::cout << fSum(10, 20) << std::endl; // 30
    std::cout << sum << std::endl; // 30
    
    return 0;
}

 

3. 사용 예제 (클래스에서 사용)

#include <iostream>
#include <functional>
#include <string>

class Person
{
public:
    Person(std::string&& pName, int pAge, std::function<std::string(std::string&, int)>&& pFunc)
        : mName(std::move(pName))
        , mAge(pAge)
        , mTagFunc(std::move(pFunc)) // 람다 함수로 전달받은 임시 함수(r-value)를 std::move를 사용해 저장
    {
        // 출력 람다 함수에서 클래스의 멤버 변수를 사용하기 위해 this를 캡처한다.
        mPrintFunc = [this]() {
            std::cout << "Name: " << mName << ", Age: " << mAge
                << ", Tag: " << mTag << std::endl;
        };
    }

    void executeTagFunc()
    {
    	// 태그 생성 람다 함수 호출
        mTag = mTagFunc(mName, mAge);
    }

    void executePrintFunc()
    {
    	// Person 정보 출력 람다 함수 호출
        mPrintFunc();
    }

private:
    std::function<std::string(std::string&, int)> mTagFunc{};
    std::function<void()> mPrintFunc{};

    std::string mName{};
    std::string mTag{};
    int mAge{0};
};

int main()
{
    // 객체 생성시 [이름], [나이], [태그 생성 함수]를 인자로 받음
    Person ps("Simson", 44, [](std::string& pName, int pAge) -> std::string {
        return std::string(pName + "-" + std::to_string(pAge));
    });

    ps.executeTagFunc();
    ps.executePrintFunc();

    return 0;
}


 

반응형
반응형

std::function은 기존의 함수 포인터를 대체하는 키워드로 C++11에서 추가되었으며, 잘 사용하면 짧은 코드로 많은 기능을 구현할 수 있다.

 

1. 사용법

#include <functional>

std::function< return-type (parameter-type) > var-name = function;

 

(1) 인자

  1-1) return-type : 함수의 실행 결과를 반환할 반환 타입을 지정

  1-2) parameter-type

    - 함수에서 사용할 인자의 타입을 지정

    - 인자가 여러 개일 경우 콤마(,)로 구분

 

2. 사용 예제 (1)

#include <iostream>
#include <functional>

void simpleFunc()
{
    std::cout << "Simple Function" << std::endl;
}

void executeFunc(std::function<void()>& func)
{
    func();
}

int main()
{
    // 기존 함수로 생성
    std::function<void()> func{simpleFunc};

    // 람다 함수로 생성
    std::function<void()> lambdaFunc{[](){
        std::cout << "Simple Lambda Function" << std::endl;
    }};

    // 함수 실행
    func();
    lambdaFunc();

    // 함수의 인자로 넘겨서 사용
    executeFunc(func);
    executeFunc(lambdaFunc);
    
    return 0;
}

 

3. 사용 예제 (2)

#include <iostream>
#include <functional>

std::function<void(unsigned char)> moveLEFT{[](unsigned char key){
    std::cout << "[" << key << "] move LEFT" << std::endl;
}};

std::function<void(unsigned char)> moveRIGHT{[](unsigned char key){
    std::cout << "[" << key << "] move RIGHT" << std::endl;
}};

std::function<void(unsigned char)> moveUP{[](unsigned char key){
    std::cout << "[" << key << "] move UP" << std::endl;
}};

std::function<void(unsigned char)> moveDOWN{[](unsigned char key){
    std::cout << "[" << key << "] move DOWN" << std::endl;
}};

void keyHandler(const unsigned char key)
{
    switch (key) {
        case 'a': return moveLEFT(key);
        case 'd': return moveRIGHT(key);
        case 'w': return moveUP(key);
        case 's': return moveDOWN(key);
    };
}

int main()
{
    unsigned char key{};

    while (true)
    {
        std::cout << "Input key: ";
        std::cin >> key;

        if (key == 'q') break;

        keyHandler(key);
    }


    return 0;
 }


 

 

반응형
반응형

기존 C++ 포인터는 동적 할당 메모리를 생성(new) 해서 사용한 후 반드시 삭제(delete) 해줘야 한다.

프로그래머가 수동으로 관리하다보니 삭제가 누락되면 메모리 릭이 발생하곤 한다.

C++11 에서 추가된 Smart Pointer를 사용하면 메모리 삭제에 대한 부분을 신경쓰지 않아도 된다.

Smart Pointer에는 3가지(unique_ptr, shared_ptr, weak_ptr)가 있으며, weak_ptr은 사용 빈도가 매우 낮으므로 unique_ptr shared_ptr 에 대해 알아보자.

 

1. 사용법

#include <memory>

std::unique_ptr< var-type > var;
std::shared_ptr< var-type > var;

 

(1) 인자

  1-1) var-type : 변수나 객체의 타입을 지정

 

2. 일반 포인터 사용

- 포인터를 생성하여 사용한 후 반드시 삭제를 해줘야 한다.

- 프로그래머의 실수로 삭제가 누락될 경우 메모리 릭이 발생할 우려가 있다.

#include <iostream>
#include <string>

using Type = std::string;

int main()
{
    Type* str{new Type("Hello world !!!")};
    std::cout << *str << std::endl;
    
    delete str; // 동적 메모리 사용 후 삭제 필요

    return 0;
}

 

3. std::unique_ptr

- 소유권을 하나만 가질 수 있는 포인터이다.

- 객체를 선언하여 사용한 후 사용 범위가 종료될 때 소멸자가 호출되어 메모리를 자동으로 삭제한다. 

- 소유권을 이전하기 위해서는 std::move()를 사용해야거나 참조로 전달해야 한다.

- C++14 이후에는 std::make_unique() 함수를 통해 포인터를 생성할 수 있다.

#include <iostream>
#include <string>
#include <memory>

using Type = std::string;

void print(std::unique_ptr<Type>& pStr4)
{
    std::unique_ptr<Type> pStr5{std::move(pStr4)};
    std::cout << pStr4 << std::endl; // 0
    std::cout << pStr5 << std::endl; // 0x141fc20
    
    // 함수가 종료되면 pStr5는 삭제됨.
}

int main()
{
    std::unique_ptr<Type> pStr{new Type("Hello world !!!")};
    //std::unique_ptr<Type> pStr{std::make_unique<Type>("Hello world !!!")}; // C++14 이후
    std::cout << pStr << std::endl; // 0x141fc20
    
    Type* ppStr{pStr.get()}; // 주소값 가져오기
    std::cout << ppStr << std::endl; // 0x141fc20
    
    //std::unique_ptr<Type> pStr2{pStr}; // 컴파일 에러!! (소유권은 하나만 가능)
    std::unique_ptr<Type> pStr3{std::move(pStr)}; // 소유권 이전 가능
    std::cout << pStr << std::endl; // 0
    std::cout << pStr3 << std::endl; // 0x141fc20
    
    print(pStr3); // 인자로 넘겨서 참조로 받기 가능
    std::cout << pStr3 << std::endl; // 0
    
    // 동적 메모리 사용 후 삭제가 필요하지 않음.

    return 0;
}

 

4. std::shared_ptr

- 소유권을 여러개 가질 수 있는 포인터이다.

- 소유자 수의 관리는 내부적으로 참조 카운터(Reference Counter)를 통해 이루어진다.

- 참조 카운터가 0이 되면 메모리를 자동으로 삭제한다.

- C++14 이후에는 std::make_shared() 함수를 통해 포인터를 생성할 수 있다.

#include <iostream>
#include <string>
#include <memory>

using Type = std::string;

void print(std::shared_ptr<Type>& pStr3)
{
    std::shared_ptr<Type> pStr4{std::move(pStr3)};
    std::cout << pStr4 << " - " << pStr4.use_count() << std::endl; // 0x2274c20 - 2
    pStr4.reset(); // 명시적으로 메모리 해제
    
    // 함수가 종료되면 참조 카운트가 1 감소됨.
}

int main()
{
    std::shared_ptr<Type> pStr{new Type("Hello world !!!")};
    //std::shared_ptr<Type> pStr{std::make_shared<Type>("Hello world !!!")}; // C++14 이후
    std::cout << pStr << " - " << pStr.use_count() << std::endl; // 0x2274c20 - 1

    Type* ppStr{pStr.get()}; // 주소값 가져오기
    std::cout << pStr << " - " << pStr.use_count() << std::endl; // 0x2274c20 - 1

    std::shared_ptr<Type> pStr2{pStr}; // 소유권 여러개 가능
    std::cout << pStr2 << " - " << pStr2.use_count() << std::endl; // 0x2274c20 - 2

    print(pStr2); // 인자로 넘겨서 참조로 받기 가능
    std::cout << pStr2 << " - " << pStr2.use_count() << std::endl; // 0 - 0

    print(pStr);
    std::cout << pStr << " - " << pStr.use_count() << std::endl; // 0 - 0
    
    // 동적 메모리 사용 후 삭제가 필요하지 않음.
    
    return 0;
}

 



 

반응형
반응형

1. 설명

- 원자적으로 처리할 변수에 대해 정의한다.

- 쓰레드 동작시 공유 변수의 동시성(Concurrency) 문제에 대해 해결해 준다.

 

 

2. 문제 상황

- 아래의 코드를 실행해보면 전역 변수 num을 2개의 쓰레드가 각각 더하거나 뺀다.

- 더하거나 빼는 과정에 문맥교환(Context Switching)이 일어나면 연산이 완료되기 전에 제어권을 빼앗길 수 있다.

- 더하거나 빼는 연산을 원자적으로 처리하기 위해 atomic 변수로 선언이 필요하다.

#include <iostream>
#include <thread>

int num{0};

void ThreadHandler(const bool isAdd)
{
    for (int i=0; i<1000000; i++)
    {
    	if (isAdd) num++;
        else num--;
    }
    
    std::cout << "num=" << num << std::endl;
}

int main()
{
    std::jthread thr1{ThreadHandler, true};
    std::jthread thr2{ThreadHandler, false};
    
    return 0;
}

 

3. 사용법

#include <atomic>

std::atomic< var-type > var;

 

(1) 인자

  1-1) var-type : 변수나 객체의 타입을 지정

 

4. 사용 예제

- 위에서 문제가 됐던 num 변수의 타입을 std::atomic<int>로 변경해 준다.

- 이제 num 변수의 원자성이 보장되기 때문에 마지막 출력값은 항상 0이 된다.

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<int> num{0}; //atomic 변수 선언

void ThreadHandler(const bool isAdd)
{
    for (int i=0; i<1000000; i++)
    {
    	if (isAdd) num.fetch_add(1); //atomic 값을 증가 (num++ 사용가능)
        else num.fetch_sub(1); //atomic 값을 감소 (num-- 사용가능)
    }
    
    std::cout << "num=" << num.load() << std::endl; //atomic 값을 출력
}

int main()
{
    num.store(0); //atomic 변수 초기화
    
    std::jthread thr1{ThreadHandler, true};
    std::jthread thr2{ThreadHandler, false};
    
    return 0;
}

 

 

 

 

반응형
반응형

CentOS 7부터 네트워크 인터페이스의 이름이 eth에서 enp로 변경되었다.

이번 글에서는 enp로 변경된 인터페이스의 이름을 eth로 변경하는 방법을 알아본다.

 

먼저, 기존 네트워크 인터페이스를 확인해본다.

$ ip addr

 

enp0s3 네트워크 인터페이스를 eth0으로 변경하기 위해 아래 파일을 편집한 후 저장한다.

$ vi /etc/default/grub
// GRUB_CMDLINE_LINUX="net.ifnames=0 biosdevname=0 rhgb quiet" 으로 변경한다.

 

 

변경한 GRUB 설정을 부팅시에 적용시키기 위해 설정 파일을 다시 생성해 준다.

$ grub2-mkconfig -o /boot/grub2/grub.cfg

 

변경한 내용을 적용시키기 위해 재부팅 한 후 네트워크 정보를 다시 출력해보면 eth0으로 변경되었다.

 

네트워크 인터페이스 이름이 변경되었으니, 실제 IP 설정 파일도 변경이 필요하다.

우선 기존 파일의 이름을 변경한다. ( ifcfg-enp0s3 → ifcfg-eth0 )

$ cd /etc/sysconfig/network-scripts
$ mv ifcfg-enp0s3 ifcfg-eth0

 

설정 파일의 내용을 변경한다.

$ vi ifcfg-eth0
// NAME, DEVICE 를 eth0으로 변경해준 후 IP 정보를 설정한다.

 

변경된 IP 정보를 적용하기 위해 아래 커맨드를 입력한다.

$ nmcli con reload
$ nmcli con up eth0

 

네트워크 정보를 다시 확인해보면 변경된 정보가 적용되어 있다.

 

 

 

 

반응형
반응형

1. 패키지 설치

rpm -ivh [package]                  # 패키지 설치

rpm -Uvh [package]                # 설치된 패키지가 있으면 삭제하고 설치

rpm -Uvh [package] --force     # 버전에 상관없이 강제 설치

rpm -Uvh [package] --nodeps  # 의존성 무시하고 설치

 

 

2. 패키지 삭제

rpm -e [package]                     # 패키지 삭제

rpm -e [package] --nodeps      # 의존성 무시하고 패키지 삭제

 

 

3. 패키지 확인

rpm -qR [package]                  # 의존성 확인

rpm -qa | grep [package]        # 설치된 패키지 확인

rpm -qi [package]                    # 패키지 정보 확인

반응형

+ Recent posts