^(코딩캣)^ = @"코딩"하는 고양이;
썸네일 이미지
Perl 로그인 로그아웃 예제 정리 (part 02 - 세션을 사용한 예) [完]
Perl 로그인 로그아웃 예제 정리 본 시리즈에서는 웹 개발을 할 때 자주 사용되는 기능인 로그인/로그아웃 기능에 대한 예제를 정리한다. 아이디와 암호가 일치할 경우 이 상태를 쿠키cookie나 세션session에 기억시키고, 이후 페이지 접속 시 해당 쿠키 또는 세션의 보유 여부에 따라 로그인한 이용자와 로그인하지 않은 이용자를 구분하고 서로 다른 페이지를 보여주도록 하는 것이 본 시리즈에서 구현하는 주된 기능이다. Perl 로그인 로그아웃 예제 정리 (part 01 - 쿠키를 사용한 예) Perl 로그인 로그아웃 예제 정리 (part 02 - 세션을 사용한 예) [完] PHP 로그인 로그아웃 예제 정리 (part 01 - 쿠키를 사용한 예) PHP 로그인 로그아웃 예제 정리 (part 02 - 세션을..
Common Gateway Interface/Perl
2018. 8. 26. 17:25

Perl 로그인 로그아웃 예제 정리 (part 02 - 세션을 사용한 예) [完]

Common Gateway Interface/Perl
2018. 8. 26. 17:25

Perl 로그인 로그아웃 예제 정리


본 시리즈에서는 웹 개발을 할 때 자주 사용되는 기능인 로그인/로그아웃 기능에 대한 예제를 정리한다. 아이디와 암호가 일치할 경우 이 상태를 쿠키cookie나 세션session에 기억시키고, 이후 페이지 접속 시 해당 쿠키 또는 세션의 보유 여부에 따라 로그인한 이용자와 로그인하지 않은 이용자를 구분하고 서로 다른 페이지를 보여주도록 하는 것이 본 시리즈에서 구현하는 주된 기능이다.

  1. Perl 로그인 로그아웃 예제 정리 (part 01 - 쿠키를 사용한 예)
  2. Perl 로그인 로그아웃 예제 정리 (part 02 - 세션을 사용한 예) [完]
  3. PHP 로그인 로그아웃 예제 정리 (part 01 - 쿠키를 사용한 예)
  4. PHP 로그인 로그아웃 예제 정리 (part 02 - 세션을 사용한 예) [完]
  5. JSP 로그인 로그아웃 예제 정리 (part 01 - 쿠키를 사용한 예)
  6. JSP 로그인 로그아웃 예제 정리 (part 02 - 세션을 사용한 예) [完]
  7. ASP 로그인 로그아웃 예제 정리 (part 01 - 쿠키를 사용한 예)
  8. ASP 로그인 로그아웃 예제 정리 (part 02 - 세션을 사용한 예) [完]
  9. ASP.NET 로그인 로그아웃 예제 정리 (part 01 - 쿠키를 사용한 예)
  10. ASP.NET 로그인 로그아웃 예제 정리 (part 02 - 세션을 사용한 예) [完]

Part II. 세션을 사용한 로그인, 로그아웃 (Perl)


본 게시물에서는 Perl(CGI) 소스 코드로 세션에 의한 로그인/로그아웃 기능을 구현한다. 로그인 화면에서 아이디와 암호를 맞게 입력했다면 서버에 로그인 정보를 보관하고 이 정보에 접근할 수 있는 식별 번호를 클라이언트의 쿠키로 전송한다. 이후 클라이언트는 페이지를 접속할 때마다 쿠키를 통해 세션 번호를 서버에 전달하고, 서버는 해당 번호의 세션이 보유한 유효성 여부를 판별하여 회원 페이지를 보여줄 것인지, 비회원 페이지를 보여줄 것인지를 결정하게 된다.

<Prologue>


CGI의 세션 기능을 사용하기 위해서는 CGI::Session 모듈을 설치해야 한다. 기본적으로 cpan install CGI::Session을 통해 설치할 수도 있지만 운영체제와의 원활한 연계를 위해 패키지 형태로 설치할 것이 권장된다.

RedHat 계열의 운영체제 (예: CentOS)는 터미널에서 다음의 명령을 실행한다.

$ sudo yum install perl-CGI-Session

Debian 계열의 운영체제 (예: Ubuntu)는 터미널에서 다음의 명령을 실행한다.

$ sudo apt install libcgi-session-perl

step1.cgi - 로그인 화면


이 페이지를 통해 사용자로부터 아이디와 암호를 입력받는다. 아이디가 전달될 매개변수 이름은 trialUsername이라 하고 암호가 전달될 매개변수 이름은 trialPassword라 이름 붙인다. 예제 소스 코드의 구조를 단순하게 하기 위하여 아이디와 암호에 대한 보안 조치 등은 생략한 채 쿠키와 POST 데이터를 통해 직접 사용자 이름과 암호가 전달될 것이다.

#!/usr/bin/perl

use CGI qw/:standard/;

print "Content-type: text/html; charset=UTF-8\n\n";

################################################################################
# 웹 페이지 본문 영역

print "<!DOCTYPE html>";
print "<html lang='ko'>";
print "<head>";
print "<meta charset='UTF-8' />";
print "<title>1 단계 : 로그인</title>";
print "</head>";
print "<body>";
print "<header>";
print "<h1>1 단계 : 로그인</h1>";
print "</header>";
print "<hr />";
print "<section>";
print "<p>사용자 이름과 사용자 암호를 입력하세요.</p>";
print "<form action='./step2.cgi' method='post'>";
print "<label for='trialUsername'>사용자 이름 : </label><input type='text' id='trialUsername' name='trialUsername' />";
print "<label for='trialPassword'>사용자 암호 : </label><input type='password' id='trialPassword' name='trialPassword' />";
print "<input type='submit' />";
print "</form>";
print "</section>";
print "<hr />";
print "<footer>";
print "<p>";
print "<span><a href='./step1.cgi'>1 단계(로그인)</a> | </span>";
print "<span><strong>2 단계(사용자 인증)</strong> | </span>";
print "<span><a href='./step3.cgi'>3 단계(회원 페이지)</a> | </span>";
print "<span><a href='./step4.cgi'>4 단계(로그아웃)</a></span>";
print "</p>";
print "</footer>";
print "</body>";
print "</html>";

[그림 1] step1.cgi 소스 코드의 실행 화면

위 소스 코드에서는 사용자 이름과 암호를 입력받는 폼form을 출력한다. name 속성이 각각 trialUsernametrialPasswordinput을 통해 사용자 이름과 암호를 입력받은 뒤 POST 방식으로 다음 파일인 step2.cgi로 전달할 것이다.

step2.cgi - 사용자 인증


이 페이지는 step1.cgi에서 전달된 아이디(trialUsername)/암호(trialPassword)가 미리 준비된 아이디(authorizedUsername)/암호(authorizedPassword)와 일치하는지 여부를 검사할 것이다. 일치하면 membershipUsernamemembershipPassword라는 세션 변수에 각각 아이디와 암호를 보관하고, 해당 세션의 식별번호를 쿠키를 통해 클라이언트로 전송할 것이다. 일치하지 않으면 별도의 오류 메시지를 보여줄 것이다.

#!/usr/bin/perl

use CGI qw/:standard/;
use CGI::Cookie;
use CGI::Session;

################################################################################
# 사용자 인증 로직
# 이름과 암호를 비교하며 승인되면 nonzero, 승인 안 되면 zero를 반환한다.

# 아래 이름의 사용자면 로그인 승인 함.
my $authorizedUsername = "codingCat";
# 아래의 암호를 입력하면 로그인 승인 함.
my $authorizedPassword = "qwerty123456!";

# 로그인 승인 시 nonzero, 아니면 zero
my $authorizeStatus = 0;

# authorizeMembership(-trialUsername, -trialPassword)
# 사용자 이름과 암호를 비교하여 로그인 승인 여부를 결정하는 서브루틴.
# -trialUsername : 로그인을 시도하는 사용자 이름
# -trialPassword : 로그인을 시도하는 사용자가 입력한 암호
# 로그인 승인하면 nonzero를 반환하고 승인 거부 시 zero를 반환한다.
sub authorizeMembership(@)
{
	my %args = @_;
	my $trialUsername = $args{"-trialUsername"};
	my $trialPassword = $args{"-trialPassword"};

	# 로그인 요청하는 사용자 이름이 미리 지정된 이름과 같은지 검사
	if ($trialUsername eq $authorizedUsername)
	{
		# 이름이 일치하면, 암호가 같은지 검사
		if ($trialPassword eq $authorizedPassword)
		{
			# 이름과 암호가 모두 일치하면 로그인 승인
			return 1;
		}
	}

	# 그렇지 않을 경우 로그인 거부
	return 0;
}

################################################################################
# 웹 페이지 헤더 영역
# 이전 페이지에서 POST 방식으로 전송된 사용자 이름 및 암호를 읽어온다.

$trialUsername = undef;
$trialPassword = undef;

# CGI 객체를 생성한다.
$objectCGI = new CGI;
# 서버에 저장된 세션 객체이다.
$objectSession = undef;
# 클라이언트로 전송할 쿠키이다.
$objectCookie = undef;

$authorizeStatus = 0;

# POST 방식으로 전달된 데이터 중 trialUsername이라는 데이터가 있는지 확인
if ($objectCGI->param("trialUsername") ne undef)
{
	# trialUsername이 존재하면, trialPassword라는 데이터도 있는지 확인 
	if ($objectCGI->param("trialPassword") ne undef)
	{
		# POST 데이터에서 얻은 사용자 이름과 암호로 로그인 상태가 유효한지 검증
		$trialUsername = $objectCGI->param("trialUsername");
		$trialPassword = $objectCGI->param("trialPassword");

		# trialUsername과 trialPassword가 모두 수신되면,
		# 앞서 정의된 authorizeMembership 메서드로 로그인 요청한다.
		$authorizeStatus = authorizeMembership
		(
			-trialUsername=>$trialUsername,
			-trialPassword=>$trialPassword
		);
	}
}

# 로그인 요청이 승인되었다면
if ($authorizeStatus)
{
	# 세션 객체를 생성한다.
	$objectSession = new CGI::Session
	(
		"driver:File",
		undef,
		{Directory=>"/tmp"}
	);

	# membershipUsername이라 이름붙인 세션 변수에
	# 사용자 이름을 기록한다.
	$objectSession->param("membershipUsername", $trialUsername);

	# membershipPassword이라 이름붙인 세션 변수에
	# 사용자 암호를 기록한다.
	$objectSession->param("membershipPassword", $trialPassword);

	# 세션의 유효 기간은 현재 시점으로부터 12시간이다.
	$objectSession->expire("+12h");

	# 현재의 편집 상태를 세션으로 저장한다.
	$objectSession->flush();

	# 세션을 생성할 때 발급된 세션 ID를 CGISESSID라는 쿠키변수로써 클라이언트에 전달한다.
	# 여기에서는 쿠키의 유효 기간도 세션과 마찬가지로 12시간으로 정한다.
	$objectCookie = new CGI::Cookie
	(
		-name=>"CGISESSID",
		-value=>($objectSession->id),
		-expires=>"+12h"
	);

	# 쿠키는 HTTP 헤더 부분에 명시되어야 하므로 웹 페이지의 본문에 앞서 이를 출력함
	print "Set-Cookie: ", $objectCookie->as_string, "\n";
}

print "Content-type: text/html; charset=UTF-8\n\n";

################################################################################
# 웹 페이지 본문 영역

print "<!DOCTYPE html>";
print "<html lang='ko'>";
print "<head>";
print "<meta charset='UTF-8' />";
print "<title>2 단계 : 사용자 인증</title>";
print "</head>";
print "<body>";
print "<header>";
print "<h1>2 단계 : 사용자 인증</h1>";
print "</header>";
print "<hr />";
print "<section>";
if ($authorizeStatus)
{
	# 로그인 승인 되었다면...
	print "<p>로그인에 성공했습니다.</p>";
	print "<p>쿠키 문자열</p>";
	print "<pre><code>", $objectCookie->as_string, "</code></pre>";
	print "<p><a href='./step3.cgi'>회원 페이지</a></p>";
}
else
{
	# 로그인 거부 되었다면...
	print "<p>로그인에 실패했습니다.</p>";
	print "<p><a href='./step1.cgi'>로그인 화면</a></p>";
}
print "</section>";
print "<hr />";
print "<footer>";
print "<p>";
print "<span><a href='./step1.cgi'>1 단계(로그인)</a> | </span>";
print "<span><strong>2 단계(사용자 인증)</strong> | </span>";
print "<span><a href='./step3.cgi'>3 단계(회원 페이지)</a> | </span>";
print "<span><a href='./step4.cgi'>4 단계(로그아웃)</a></span>";
print "</p>";
print "</footer>";
print "</body>";
print "</html>";

[그림 2] step2.cgi 소스 코드의 실행 화면 (로그인 성공)

[그림 3] step2.cgi 소스 코드의 실행 화면 (로그인 실패)

로그인이 정상적이라면 서버의 특정 경로에 세션에 대한 데이터가 생성되어있음을 확인할 수 있다. CentOS를 기준으로 /tmp/systemd-private-...-httpd.service-.../<디렉터리명>이다. 여기서 <디렉터리명>란 perl 코드에서 세션 객체를 생성할 때 { Directory=> "..." }로 지정한 이름이다. 세션이 유지되는 동안 root 권한으로 이 디렉터리를 들어가면 cgisess_<세션식별번호>라는 이름의 텍스트 파일이 아래와 같이 존재할 것이다.

[그림 4] 세션 정보를 담은 텍스트 파일의 확인

vi로 이 파일을 열었을 때 다음과 같이 사용자 이름, 사용자 암호, 만료시간 등의 각종 세션변수가 기록되어 있음을 확인할 수 있다. 쿠키를 사용하여 클라이언트로 전달되는 내용은 아래의 내용 자체가 아니라 아래의 내용에 접근할 수 있는 고유번호만을 전달할 뿐이므로 쿠키만을 사용하는 로그인보다는 안전하다고 볼 수 있다.

[그림 5] 세션 파일의 내용

step3.cgi - 회원 전용 페이지


클라이언트 측으로부터 쿠키에 보관된 세션 식별 번호를 전달받는다. 서버의 특정 위치에서 해당 번호를 갖는 세션을 읽어온 후, 로그인 상태임이 확인되면 회원 전용 페이지를 출력한다. 그렇지 않다면 비회원용 페이지를 출력한다.

#!/usr/bin/perl

use CGI qw/:standard/;
use CGI::Cookie;
use CGI::Session;

################################################################################
# 사용자 인증 로직
# 이름과 암호를 비교하며 승인되면 nonzero, 승인 안 되면 zero를 반환한다.

# 아래 이름의 사용자면 로그인 승인 함.
my $authorizedUsername = "codingCat";
# 아래의 암호를 입력하면 로그인 승인 함.
my $authorizedPassword = "qwerty123456!";

# 로그인 승인 시 nonzero, 아니면 zero
my $authorizeStatus = 0;

# authorizeMembership(-trialUsername, -trialPassword)
# 사용자 이름과 암호를 비교하여 로그인 승인 여부를 결정하는 서브루틴.
# -trialUsername : 로그인을 시도하는 사용자 이름
# -trialPassword : 로그인을 시도하는 사용자가 입력한 암호
# 로그인 승인하면 nonzero를 반환하고 승인 거부 시 zero를 반환한다.
sub authorizeMembership(@)
{
	my %args = @_;
	my $trialUsername = $args{"-trialUsername"};
	my $trialPassword = $args{"-trialPassword"};

	# 로그인 요청하는 사용자 이름이 미리 지정된 이름과 같은지 검사
	if ($trialUsername eq $authorizedUsername)
	{
		# 이름이 일치하면, 암호가 같은지 검사
		if ($trialPassword eq $authorizedPassword)
		{
			# 이름과 암호가 모두 일치하면 로그인 승인
			return 1;
		}
	}

	# 그렇지 않을 경우 로그인 거부
	return 0;
}

################################################################################
# 웹 페이지 헤더 영역
# 세션에 저장된 사용자 이름 및 암호를 읽어온다.

$membershipUsername = undef;
$membershipPassword = undef;

# CGI 객체를 생성한다.
$objectCGI = new CGI;
# 서버에 저장된 세션 객체이다.
$objectSession = undef;
# 클라이언트로 전송할 쿠키이다.
$objectCookie = undef;
# 본 페이지로 전달된 쿠키를 확인한다.
%objectCookies = fetch CGI::Cookie;

$authorizeStatus = 0;

# 쿠키가 존재하면
# 로그인 유효성 검사 : 클라이언트의 쿠키가 본 페이지에 전달한 로그인 정보를 검증한다.
if (%objectCookies ne undef)
{
	# 쿠키 변수 중에 CGISESSID라는 데이터가 있는지 확인
	if ($objectCookies{"CGISESSID"} ne undef)
	{
		# 세션 객체를 생성한다.
		$objectSession = new CGI::Session
		(
			undef,
			(scalar $objectCookies{"CGISESSID"}->value),
			{Directory=>"/tmp"}
		);

		# 세션 변수 중 membershipUsername이라는 데이터가 있는지 확인
		if ($objectSession->param("membershipUsername") ne undef)
		{
			# membershipUsername이 존재하면, membershipPassword라는 데이터도 있는지 확인 
			if ($objectSession->param("membershipPassword") ne undef)
			{
				# 세션 변수에서 얻은 사용자 이름과 암호로 로그인 상태가 유효한지 검증
				$membershipUsername = $objectSession->param("membershipUsername");
				$membershipPassword = $objectSession->param("membershipPassword");

				# membershipUsername과 membershipPassword가 모두 수신되면,
				# 앞서 정의된 authorizeMembership 메서드로 로그인 요청한다.
				$authorizeStatus = authorizeMembership
				(
					-trialUsername=>$membershipUsername,
					-trialPassword=>$membershipPassword
				);
			}
		}
	}
}

# 로그인 요청이 승인되었다면
if ($authorizeStatus)
{
	# membershipUsername이라 이름붙인 세션 변수에
	# 사용자 이름을 기록한다.
	$objectSession->param("membershipUsername", $membershipUsername);

	# membershipPassword이라 이름붙인 세션 변수에
	# 사용자 암호를 기록한다.
	$objectSession->param("membershipPassword", $membershipPassword);

	# 세션의 유효 기간은 현재 시점으로부터 12시간이다.
	$objectSession->expire("+12h");

	# 현재의 편집 상태를 세션으로 저장한다.
	$objectSession->flush();
	
	# 세션을 생성할 때 발급된 세션 ID를 CGISESSID라는 쿠키 변수로써 클라이언트에 전달한다.
	# 여기에서는 쿠키의 유효 기간도 세션과 마찬가지로 12시간으로 정한다.
	$objectCookie = new CGI::Cookie
	(
		-name=>"CGISESSID",
		-value=>($objectSession->id),
		-expires=>"+12h"
	);

	# 쿠키는 HTTP 헤더 부분에 명시되어야 하므로 웹 페이지의 본문에 앞서 이를 출력함
	print "Set-Cookie: ", $objectCookie->as_string, "\n";
}

print "Content-type: text/html; charset=UTF-8\n\n";

################################################################################
# 웹 페이지 본문 영역

print "<!DOCTYPE html>";
print "<html lang='ko'>";
print "<head>";
print "<meta charset='UTF-8' />";
print "<title>3 단계 : 회원 페이지</title>";
print "</head>";
print "<body>";
print "<header>";
print "<h1>3 단계 : 회원 페이지</h1>";
print "</header>";
print "<hr />";
print "<section>";
if ($authorizeStatus)
{
	# 로그인 승인 되었다면...
	print "<p>회원 전용 페이지</p>";
	print "<p>환영합니다. $membershipUsername 님.</p>";
	print "<p>쿠키 문자열 :</p>";
	print "<pre><code>$ENV{'HTTP_COOKIE'}</code></pre>";
	print "<p><a href='./step4.cgi'>로그아웃</a></p>";
}
else
{
	# 로그인 거부 되었다면...
	print "<p>이 페이지를 보려면 로그인이 필요합니다.</p>";
	print "<p><a href='./step1.cgi'>로그인</a></p>";
}
print "</section>";
print "<hr />";
print "<footer>";
print "<p>";
print "<span><a href='./step1.cgi'>1 단계(로그인)</a> | </span>";
print "<span><strong>2 단계(사용자 인증)</strong> | </span>";
print "<span><a href='./step3.cgi'>3 단계(회원 페이지)</a> | </span>";
print "<span><a href='./step4.cgi'>4 단계(로그아웃)</a></span>";
print "</p>";
print "</footer>";
print "</body>";
print "</html>";

 

[그림 6] step3.cgi 소스 코드의 실행 화면 (로그인 성공)

 

[그림 7] step3.cgi 소스 코드의 실행 화면 (로그인 실패)

step4.cgi - 로그아웃 화면


로그인 상태임이 확인되면 서버의 세션을 삭제하고 클라이언트의 쿠키를 만료시켜서 로그아웃 작업을 수행한다. 그렇지 않은 경우 이미 로그아웃 상태임을 알려준다.

#!/usr/bin/perl

use CGI qw/:standard/;
use CGI::Cookie;
use CGI::Session;

################################################################################
# 사용자 인증 로직
# 이름과 암호를 비교하며 승인되면 nonzero, 승인 안 되면 zero를 반환한다.

# 아래 이름의 사용자면 로그인 승인 함.
my $authorizedUsername = "codingCat";
# 아래의 암호를 입력하면 로그인 승인 함.
my $authorizedPassword = "qwerty123456!";

# 로그인 승인 시 nonzero, 아니면 zero
my $authorizeStatus = 0;

# authorizeMembership(-trialUsername, -trialPassword)
# 사용자 이름과 암호를 비교하여 로그인 승인 여부를 결정하는 서브루틴.
# -trialUsername : 로그인을 시도하는 사용자 이름
# -trialPassword : 로그인을 시도하는 사용자가 입력한 암호
# 로그인 승인하면 nonzero를 반환하고 승인 거부 시 zero를 반환한다.
sub authorizeMembership(@)
{
	my %args = @_;
	my $trialUsername = $args{"-trialUsername"};
	my $trialPassword = $args{"-trialPassword"};

	# 로그인 요청하는 사용자 이름이 미리 지정된 이름과 같은지 검사
	if ($trialUsername eq $authorizedUsername)
	{
		# 이름이 일치하면, 암호가 같은지 검사
		if ($trialPassword eq $authorizedPassword)
		{
			# 이름과 암호가 모두 일치하면 로그인 승인
			return 1;
		}
	}

	# 그렇지 않을 경우 로그인 거부
	return 0;
}

################################################################################
# 웹 페이지 헤더 영역
# 클라이언트의 쿠키로부터 전송된 사용자 이름 및 암호를 읽어온다.

$membershipUsername = undef;
$membershipPassword = undef;

# CGI 객체를 생성한다.
$objectCGI = new CGI;
# 서버에 저장된 세션 객체이다.
$objectSession = undef;
# 클라이언트로 전송할 쿠키이다.
$objectCookie = undef;
# 본 페이지로 전달된 쿠키를 확인한다.
%objectCookies = fetch CGI::Cookie;

$authorizeStatus = 0;

# 쿠키가 존재하면
# 로그인 유효성 검사 : 클라이언트의 쿠키가 본 페이지에 전달한 로그인 정보를 검증한다.
if (%objectCookies ne undef)
{
	# 쿠키 변수 중에 CGISESSID라는 데이터가 있는지 확인
	if ($objectCookies{"CGISESSID"} ne undef)
	{
		# 세션 객체를 생성한다.
		$objectSession = new CGI::Session
		(
			undef,
			(scalar $objectCookies{"CGISESSID"}->value),
			{Directory=>"/tmp"}
		);

		# 세션 변수 중 membershipUsername이라는 데이터가 있는지 확인
		if ($objectSession->param("membershipUsername") ne undef)
		{
			# membershipUsername이 존재하면, membershipPassword라는 데이터도 있는지 확인
			if ($objectSession->param("membershipPassword") ne undef)
			{
				# 세션 변수에서 얻은 사용자 이름과 암호로 로그인 상태가 유효한지 검증
				$membershipUsername = $objectSession->param("membershipUsername");
				$membershipPassword = $objectSession->param("membershipPassword");

				# membershipUsername과 membershipPassword가 모두 수신되면,
				# 앞서 정의된 authorizeMembership 메서드로 로그인 요청한다.
				$authorizeStatus = authorizeMembership
				(
					-trialUsername=>$membershipUsername,
					-trialPassword=>$membershipPassword
				);
			}
		}
	}
}

# 로그아웃은 로그인 여부와 무관하게 수행된다.

# membershipUsername이라 이름붙인 세션 변수에
# 사용자 이름을 기록한다.
$objectSession->param("membershipUsername", "<undefined>");

# membershipPassword이라 이름붙인 세션 변수에
# 사용자 암호를 기록한다.
$objectSession->param("membershipPassword", "<undefined>");

# 세션 객체를 삭제함
$objectSession->delete();

# 클라이언트에도 이미 삭제된 세션 ID가 남아있지 않도록 쿠키를 강제 만료
$objectCookie = new CGI::Cookie
(
	-name=>"CGISESSID",
	-value=>"under",
	-expires=>"-12h"
);

# 쿠키는 HTTP 헤더 부분에 명시되어야 하므로 웹 페이지의 본문에 앞서 이를 출력함
print "Set-Cookie: ", $objectCookie->as_string, "\n";

print "Content-type: text/html; charset=UTF-8\n\n";

################################################################################
# 웹 페이지 본문 영역

print "<!DOCTYPE html>";
print "<html lang='ko'>";
print "<head>";
print "<meta charset='UTF-8' />";
print "<title>4 단계 : 로그아웃</title>";
print "<style>a { color: #0000FF; }</style>";
print "</head>";
print "<body>";
print "<header>";
print "<h1>4 단계 : 로그아웃</h1>";
print "</header>";
print "<hr />";
print "<section>";
if ($authorizeStatus)
{
	# 로그인 된 상태였다면...
	print "<p>로그아웃되었습니다.</p>";
	print "<p><a href='./step1.cgi'>로그인</a></p>";
}
else
{
	# 로그인 안 된 상태였다면...
	print "<p>로그인되어있지 않습니다.</p>";
	print "<p><a href='./step1.cgi'>로그인</a></p>";
}
print "</section>";
print "<hr />";
print "<footer>";
print "<p>";
print "<a href='./step1.cgi'>1 단계(로그인)</a> |";
print "<a href='./step2.cgi'>2 단계(사용자 인증)</a> | ";
print "<a href='./step3.cgi'>3 단계(회원 페이지)</a> | ";
print "<strong>4 단계(로그아웃)</string>";
print "</p>";
print "</footer>";
print "</body>";
print "</html>";

[그림 8] step4.cgi 소스 코드의 실행 화면 (로그아웃 성공)

[그림 9] step4.cgi 소스 코드의 실행 화면 (이미 로그아웃된 상태)

본 소스 코드의 실행 결과 서버에 보관되어있던 세션도 삭제되었음을 확인할 수 있다.

[그림 10] step4.cgi 소스 코드의 실행 결과 세션이 삭제된 상태

step1.cgi - 중복 로그인을 방지하기 위한 소스 코드의 수정


앞서 작성한 step3.cgi을 살펴보면, 세션으로부터 사용자 이름과 암호를 읽은 후 로그인 유효성을 재검증하는 부분이 있다. 로그인이 재차 승인되면 쿠키의 유효기간이 연장되면서 회원 전용 페이지를 보여주고 그렇지 않으면 로그인 화면으로 안내하는 메시지를 보여주는데 이를 step1.cgi에 적용해본다. 중복 로그인을 방지하기 위해 쿠키를 읽어들이고 이미 로그인이 유효한 상태라면 곧바로 회원 페이지로 넘어갈 수 있도록 안내하는 기능이 추가된다.

#!/usr/bin/perl

use CGI qw/:standard/;
use CGI::Cookie;

################################################################################
# 사용자 인증 로직
# 이름과 암호를 비교하며 승인되면 nonzero, 승인 안 되면 zero를 반환한다.

# 아래 이름의 사용자면 로그인 승인 함.
my $authorizedUsername = "codingCat";
# 아래의 암호를 입력하면 로그인 승인 함.
my $authorizedPassword = "qwerty123456!";

# 로그인 승인 시 nonzero, 아니면 zero
my $authorizeStatus = 0;

# authorizeMembership(-trialUsername, -trialPassword)
# 사용자 이름과 암호를 비교하여 로그인 승인 여부를 결정하는 서브루틴.
# -trialUsername : 로그인을 시도하는 사용자 이름
# -trialPassword : 로그인을 시도하는 사용자가 입력한 암호
# 로그인 승인하면 nonzero를 반환하고 승인 거부 시 zero를 반환한다.
sub authorizeMembership(@)
{
	my %args = @_;
	my $trialUsername = $args{"-trialUsername"};
	my $trialPassword = $args{"-trialPassword"};

	# 로그인 요청하는 사용자 이름이 미리 지정된 이름과 같은지 검사
	if ($trialUsername eq $authorizedUsername)
	{
		# 이름이 일치하면, 암호가 같은지 검사
		if ($trialPassword eq $authorizedPassword)
		{
			# 이름과 암호가 모두 일치하면 로그인 승인
			return 1;
		}
	}

	# 그렇지 않을 경우 로그인 거부
	return 0;
}

################################################################################
# 웹 페이지 헤더 영역
# 세션에 기록된 사용자 이름 및 암호를 읽어와서 로그인 유효성을 검증한다.

$membershipUsername = undef;
$membershipPassword = undef;

# CGI 객체를 생성한다.
$objectCGI = new CGI;
# 본 페이지로 전달된 쿠키를 확인한다.
%objectCookies = fetch CGI::Cookie;

# 쿠키가 존재하면
# 로그인 유효성 검사 : 클라이언트의 쿠키가 본 페이지에 전달한 로그인 정보를 검증한다.
if (%objectCookies ne undef)
{
	# 쿠키 변수 중 CGISESSID라는 데이터가 있는지 확인
	if ($objectCookies{"CGISESSID"} ne undef)
	{
		# CGISESSID가 있으면 세션 객체를 불러온다.
		$objectSession = new CGI::Session
		(
			undef,
			(scalar $objectCookies{"CGISESSID"}->value),
			{Directory=>"/tmp"}
		);

		# 세션 변수 중 membershipUsername이라는 데이터가 있는지 확인
		if ($objectSession->param("membershipUsername") ne undef)
		{
			# membershipUsername이 존재하면, membershipPassword라는 데이터도 있는지 확인 
			if ($objectSession->param("membershipPassword") ne undef)
			{
				# 세션 변수에서 얻은 사용자 이름과 암호로 로그인 상태가 유효한지 검증
				$membershipUsername = $objectSession->param("membershipUsername");
				$membershipPassword = $objectSession->param("membershipPassword");

				# membershipUsername과 membershipPassword가 모두 수신되면,
				# 앞서 정의된 authorizeMembership 메서드로 로그인 요청한다.
				$authorizeStatus = authorizeMembership
				(
					-trialUsername=>$membershipUsername,
					-trialPassword=>$membershipPassword
				);
			}
		}
	}
}

# 로그인 요청이 승인되었다면
if ($authorizeStatus)
{
	# membershipUsername이라 이름붙인 세션 변수에
	# 사용자 이름을 기록한다.
	$objectSession->param("membershipUsername", $membershipUsername);

	# membershipPassword이라 이름붙인 세션 변수에
	# 사용자 암호를 기록한다.
	$objectSession->param("membershipPassword", $membershipPassword);

	# 세션의 유효 기간은 현재 시점으로부터 12시간이다.
	$objectSession->expire("+12h");

	# 현재의 편집 상태를 세션으로 저장한다.
	$objectSession->flush();

	# 세션을 생성할 때 발급된 세션 ID를 CGISESSID라는 쿠키 변수로써 클라이언트에 전달한다.
	# 여기에서는 쿠키의 유효 기간도 세션과 마찬가지로 12시간으로 정한다.
	$objectCookie = new CGI::Cookie
	(
		-name=>"CGISESSID",
		-value=>($objectSession->id),
		-expires=>"+12h"
	);

	# 쿠키는 HTTP 헤더 부분에 명시되어야 하므로 웹 페이지의 본문에 앞서 이를 출력함
	print "Set-Cookie: ", $objectCookie->as_string, "\n";
}

print "Content-type: text/html; charset=UTF-8\n\n";

################################################################################
# 웹 페이지 본문 영역

print "<!DOCTYPE html>";
print "<html lang='ko'>";
print "<head>";
print "<meta charset='UTF-8' />";
print "<title>1 단계 : 로그인</title>";
print "<style>a { color: #0000FF; }</style>";
print "</head>";
print "<body>";
print "<header>";
print "<h1>1 단계 : 로그인</h1>";
print "</header>";
print "<hr />";
print "<section>";
if ($authorizeStatus)
{
	# 로그인 승인 되었다면...
	print "<p>이미 로그인되어 있습니다.</p>";
	print "<p><a href='step3.cgi'>회원 페이지</a></p>";
}
else
{
	# 로그인 거부 되었다면...
	print "<p>사용자 이름과 사용자 암호를 입력하세요.</p>";
	print "<form action='./step2.cgi' method='post'>";
	print "<label for='trialUsername'>사용자 이름 : </label><input type='text' id='trialUsername' name='trialUsername' />";
	print "<label for='trialPassword'>사용자 암호 : </label><input type='password' id='trialPassword' name='trialPassword' />";
	print "<input type='submit' />";
	print "</form>";
}
print "</section>";
print "<hr />";
print "<footer>";
print "<p>";
print "<span><a href='./step1.cgi'>1 단계(로그인)</a> | </span>";
print "<span><strong>2 단계(사용자 인증)</strong> | </span>";
print "<span><a href='./step3.cgi'>3 단계(회원 페이지)</a> | </span>";
print "<span><a href='./step4.cgi'>4 단계(로그아웃)</a></span>";
print "</p>";
print "</footer>";
print "</body>";
print "</html>";

[그림 11] 로그인이 유효한 상태에서 접속하였을 때, step1.cgi 소스 코드의 출력 결과
카테고리 “Common Gateway Interface/Perl”
more...
썸네일 이미지
Perl 로그인 로그아웃 예제 정리 (part 01 - 쿠키를 사용한 예)
Perl 로그인 로그아웃 예제 정리 본 시리즈에서는 웹 개발을 할 때 자주 사용되는 기능인 로그인/로그아웃 기능에 대한 예제를 정리한다. 아이디와 암호가 일치할 경우 이 상태를 쿠키cookie나 세션session에 기억시키고, 이후 페이지 접속 시 해당 쿠키 또는 세션의 보유 여부에 따라 로그인한 이용자와 로그인하지 않은 이용자를 구분하고 서로 다른 페이지를 보여주도록 하는 것이 본 시리즈에서 구현하는 주된 기능이다. Perl 로그인 로그아웃 예제 정리 (part 01 - 쿠키를 사용한 예) Perl 로그인 로그아웃 예제 정리 (part 02 - 세션을 사용한 예) [完] PHP 로그인 로그아웃 예제 정리 (part 01 - 쿠키를 사용한 예) PHP 로그인 로그아웃 예제 정리 (part 02 - 세션을..
Common Gateway Interface/Perl
2018. 8. 23. 13:19

Perl 로그인 로그아웃 예제 정리 (part 01 - 쿠키를 사용한 예)

Common Gateway Interface/Perl
2018. 8. 23. 13:19

Perl 로그인 로그아웃 예제 정리


본 시리즈에서는 웹 개발을 할 때 자주 사용되는 기능인 로그인/로그아웃 기능에 대한 예제를 정리한다. 아이디와 암호가 일치할 경우 이 상태를 쿠키cookie나 세션session에 기억시키고, 이후 페이지 접속 시 해당 쿠키 또는 세션의 보유 여부에 따라 로그인한 이용자와 로그인하지 않은 이용자를 구분하고 서로 다른 페이지를 보여주도록 하는 것이 본 시리즈에서 구현하는 주된 기능이다.

  1. Perl 로그인 로그아웃 예제 정리 (part 01 - 쿠키를 사용한 예)
  2. Perl 로그인 로그아웃 예제 정리 (part 02 - 세션을 사용한 예) [完]
  3. PHP 로그인 로그아웃 예제 정리 (part 01 - 쿠키를 사용한 예)
  4. PHP 로그인 로그아웃 예제 정리 (part 02 - 세션을 사용한 예) [完]
  5. JSP 로그인 로그아웃 예제 정리 (part 01 - 쿠키를 사용한 예)
  6. JSP 로그인 로그아웃 예제 정리 (part 02 - 세션을 사용한 예) [完]
  7. ASP 로그인 로그아웃 예제 정리 (part 01 - 쿠키를 사용한 예)
  8. ASP 로그인 로그아웃 예제 정리 (part 02 - 세션을 사용한 예) [完]
  9. ASP.NET 로그인 로그아웃 예제 정리 (part 01 - 쿠키를 사용한 예)
  10. ASP.NET 로그인 로그아웃 예제 정리 (part 02 - 세션을 사용한 예) [完]

Part I. 쿠키를 사용한 로그인, 로그아웃 (Perl)


본 게시물에서는 Perl(CGI) 소스 코드로 쿠키에 의한 로그인/로그아웃 기능을 구현한다. 로그인 화면에서 아이디와 암호를 맞게 입력했다면 이 정보를 클라이언트의 쿠키로 전송한다. 이후 클라이언트는 페이지를 접속할 때마다 쿠키를 서버에 전달하고, 서버는 쿠키의 유효성 여부를 판별하여 회원 페이지를 보여줄 것인지, 비회원 페이지를 보여줄 것인지를 결정하게 된다.

step1.cgi - 로그인 화면


이 페이지를 통해 사용자로부터 아이디와 암호를 입력받는다. 아이디가 전달될 매개변수 이름은 trialUsername이라 하고 암호가 전달될 매개변수 이름은 trialPassword라 이름 붙인다. 예제 소스 코드의 구조를 단순하게 하기 위하여 아이디와 암호에 대한 보안 조치 등은 생략한 채 쿠키와 POST 데이터를 통해 직접 사용자 이름과 암호가 전달될 것이다.

#!/usr/bin/perl

use CGI qw/:standard/;

print "Content-type: text/html; charset=UTF-8\n\n";

################################################################################
# 웹 페이지 본문 영역

print "<!DOCTYPE html>";
print "<html lang='ko'>";
print "<head>";
print "<meta charset='UTF-8' />";
print "<title>1 단계 : 로그인</title>";
print "</head>";
print "<body>";
print "<header>";
print "<h1>1 단계 : 로그인</h1>";
print "</header>";
print "<hr />";
print "<section>";
print "<p>사용자 이름과 사용자 암호를 입력하세요.</p>";
print "<form action='./step2.cgi' method='post'>";
print "<label for='trialUsername'>사용자 이름 : </label><input type='text' id='trialUsername' name='trialUsername' />";
print "<label for='trialPassword'>사용자 암호 : </label><input type='password' id='trialPassword' name='trialPassword' />";
print "<input type='submit' />";
print "</form>";
print "</section>";
print "<hr />";
print "<footer>";
print "<p>";
print "<span><strong>1 단계(로그인)</strong> | </span>";
print "<span><a href='./step2.cgi'>2 단계(사용자 인증)</a> | </span>";
print "<span><a href='./step3.cgi'>3 단계(회원 페이지)</a> | </span>";
print "<span><a href='./step4.cgi'>4 단계(로그아웃)</a></span>";
print "</p>";
print "</footer>";
print "</body>";
print "</html>";

[그림 1] step1.cgi 소스 코드의 실행 화면

위 소스 코드에서는 사용자 이름과 암호를 입력받는 폼form을 출력한다. name 속성이 각각 trialUsernametrialPasswordinput을 통해 사용자 이름과 암호를 입력받은 뒤 POST 방식으로 다음 파일인 step2.cgi로 전달할 것이다.

step2.cgi - 사용자 인증


이 페이지는 step1.cgi에서 전달된 아이디(trialUsername)/암호(trialPassword)가 미리 준비된 아이디(authorizedUsername)/암호(authorizedPassword)와 일치하는지 여부를 검사할 것이다. 일치하면 membershipUsernamemembershipPassword라는 쿠키 변수에 각각 아이디와 암호를 보관하여 클라이언트로 전송할 것이다. 일치하지 않으면 별도의 오류 메시지를 보여줄 것이다.

#!/usr/bin/perl

use CGI qw/:standard/;
use CGI::Cookie;

################################################################################
# 사용자 인증 로직
# 이름과 암호를 비교하며 승인되면 nonzero, 승인 안 되면 zero를 반환한다.

# 아래 이름의 사용자면 로그인 승인 함.
my $authorizedUsername = "codingCat";
# 아래의 암호를 입력하면 로그인 승인 함.
my $authorizedPassword = "qwerty123456!";

# 로그인 승인 시 nonzero, 아니면 zero
my $authorizeStatus = 0;

# authorizeMembership(-trialUsername, -trialPassword)
# 사용자 이름과 암호를 비교하여 로그인 승인 여부를 결정하는 서브루틴.
# -trialUsername : 로그인을 시도하는 사용자 이름
# -trialPassword : 로그인을 시도하는 사용자가 입력한 암호
# 로그인 승인하면 nonzero를 반환하고 승인 거부 시 zero를 반환한다.
sub authorizeMembership(@)
{
	my %args = @_;
	my $trialUsername = $args{"-trialUsername"};
	my $trialPassword = $args{"-trialPassword"};

	# 로그인 요청하는 사용자 이름이 미리 지정된 이름과 같은지 검사
	if ($trialUsername eq $authorizedUsername)
	{
		# 이름이 일치하면, 암호가 같은지 검사
		if ($trialPassword eq $authorizedPassword)
		{
			# 이름과 암호가 모두 일치하면 로그인 승인
			return 1;
		}
	}

	# 그렇지 않을 경우 로그인 거부
	return 0;
}

################################################################################
# 웹 페이지 헤더 영역
# 이전 페이지에서 POST 방식으로 전송된 사용자 이름 및 암호를 읽어온다.

$trialUsername = undef;
$trialPassword = undef;

# CGI 객체를 생성한다.
$objectCGI = new CGI;
# 클라이언트로 전송할 쿠키이다.
$objectUsername = undef;
# 클라이언트로 전송할 쿠키이다.
$objectPassword = undef;

# POST 방식으로 전달된 데이터 중 trialUsername이라는 데이터가 있는지 확인
if ($objectCGI->param("trialUsername") ne undef)
{

	# trialUsername이 존재하면, trialPassword라는 데이터도 있는지 확인 
	if ($objectCGI->param("trialPassword") ne undef)
	{
		# POST 데이터에서 얻은 사용자 이름과 암호로 로그인 상태가 유효한지 검증
		$trialUsername = $objectCGI->param("trialUsername");
		$trialPassword = $objectCGI->param("trialPassword");

		# trialUsername과 trialPassword가 모두 수신되면,
		# 앞서 정의된 authorizeMembership 메서드로 로그인 요청한다.
		$authorizeStatus = authorizeMembership
		(
			-trialUsername=>$trialUsername,
			-trialPassword=>$trialPassword
		);
	}
}

# 로그인 요청이 승인되었다면
if ($authorizeStatus)
{
	# membershipUsername이라 이름붙인 유효기간 12시간짜리 쿠키 변수에 
	# 사용자 이름을 기록한다.
	$objectUsername = new CGI::Cookie
	(
		-name=>"membershipUsername",
		-value=>$trialUsername,
		-expires=>"+12h"
	);

	# membershipPassword이라 이름붙인 유효기간 12시간짜리 쿠키 변수에 
	# 사용자 암호를 기록한다.
	$objectPassword = new CGI::Cookie
	(
		-name=>"membershipPassword",
		-value=>$trialPassword,
		-expires=>"+12h"
	);

	# 쿠키는 HTTP 헤더 부분에 명시되어야 하므로 웹 페이지의 본문에 앞서 이를 출력함
	print "Set-Cookie: ", $objectUsername->as_string, "\n";
	print "Set-Cookie: ", $objectPassword->as_string, "\n";
}

print "Content-type: text/html; charset=UTF-8\n\n";

################################################################################
# 웹 페이지 본문 영역

print "<!DOCTYPE html>";
print "<html lang='ko'>";
print "<head>";
print "<meta charset='UTF-8' />";
print "<title>2 단계 : 사용자 인증</title>";
print "</head>";
print "<body>";
print "<header>";
print "<h1>2 단계 : 사용자 인증</h1>";
print "</header>";
print "<hr />";
print "<section>";
if ($authorizeStatus)
{
	# 로그인 승인 되었다면...
	print "<p>로그인에 성공했습니다.</p>";
	print "<p>쿠키 문자열</p>";
	print "<pre><code>", $objectUsername->as_string, "</code></pre>";
	print "<pre><code>", $objectPassword->as_string, "</code></pre>";
	print "<p><a href='./step3.cgi'>회원 페이지</a></p>";
}
else
{
	# 로그인 거부 되었다면...
	print "<p>로그인에 실패했습니다.</p>";
	print "<p><a href='./step1.cgi'>로그인 화면</a></p>";
}
print "</section>";
print "<hr />";
print "<footer>";
print "<p>";
print "<span><a href='./step1.cgi'>1 단계(로그인)</a> | </span>";
print "<span><strong>2 단계(사용자 인증)</strong> | </span>";
print "<span><a href='./step3.cgi'>3 단계(회원 페이지)</a> | </span>";
print "<span><a href='./step4.cgi'>4 단계(로그아웃)</a></span>";
print "</p>";
print "</footer>";
print "</body>";
print "</html>";

[그림 2] step2.cgi 소스 코드의 실행 화면 (로그인 성공)

[그림 3] step2.cgi 소스 코드의 실행 화면 (로그인 실패)

위 소스 코드는 이전 페이지(step1.cgi)에서 POST 방식으로 전달된 두 데이터(trialUsername, trialPassword)를 읽어와서 로그인을 수행하는 예이다. 통상 이런 종류의 예는 회원 정보를 기록하고 있는 데이터베이스에 접속하여 아이디와 패스워드를 비교하지만, 소스 코드의 단순화를 위해 이를 하드코딩하였다.

로그인을 허용할 사용자의 이름이 "codingCat"이고 이를 변수 $authorizedUsername에 보관한다. 마찬가지로 접속 암호는 "qwerty123456!"이라 하고 이를 변수 $authorizedPassword에 보관한다.

로그인 유효성 검사를 수행할 서브루틴은 authorizeMembership(-trialUsername, -trialPassword);으로 정의하였다. 이 서브루틴에서는 매개변수(-trialUsername, -trialPassword)로 받아들인 사용자 이름과 암호가 미리 정의된 값($authorizedUsername, $authorizedPassword)과 동일한지 여부를 검사하여 일치하면 non-zero를 반환하고 일치하지 않으면 zero를 반환한다.

authorizeMembership 서브루틴이 non-zero를 반환하면 이를 변수 $authorizeStatus에 보관한다. 이 값의 non-zero에 따라 웹 페이지 본문에서 로그인 성공 또는 실패 화면 중 하나를 선택하여 보여주게 될 것이다. 이 때 중요한 것은 쿠키의 생성이다. 쿠키는 HTTP 헤더 부분에 Set-Cookie 항목으로써 나타나야 한다. 즉, 웹 페이지 본문을 출력하는 도중에 쿠키를 클라이언트로 전송하는 것이 불가능하다. 그러므로 로그인 승인 여부를 가장 처음에 확인한 후 웹 페이지를 시작하는 것이다. 로그인 성공 시 유효기간 12시간짜리의 membershipUsername, membershipPassword 쿠키변수를 생성하여 로그인이 허용된 사용자 이름과 암호를 보관하고 이를 클라이언트로 전달한다.

로그인이 성공하면 서버에서 클라이언트로 쿠키가 전달되는데, 그 내용은 다음과 같이 확인 가능하며 본 소스 코드에서 기록한 사용자 이름과 암호를 그대로 볼 수 있음을 확인할 수 있다.

[그림 4] step2.cgi 소스 코드의 실행 결과 생성된 쿠키

step3.cgi - 회원 전용 페이지


클라이언트 측으로부터 쿠키에 보관된 아이디와 암호를 전달받는다. 로그인 상태임이 확인되면 회원 전용 페이지를 출력하고, 그렇지 않다면 비회원용 페이지를 출력한다.

#!/usr/bin/perl

use CGI qw/:standard/;
use CGI::Cookie;

################################################################################
# 사용자 인증 로직
# 이름과 암호를 비교하며 승인되면 nonzero, 승인 안 되면 zero를 반환한다.

# 아래 이름의 사용자면 로그인 승인 함.
my $authorizedUsername = "codingCat";
# 아래의 암호를 입력하면 로그인 승인 함.
my $authorizedPassword = "qwerty123456!";

# 로그인 승인 시 nonzero, 아니면 zero
my $authorizeStatus = 0;

# authorizeMembership(-trialUsername, -trialPassword)
# 사용자 이름과 암호를 비교하여 로그인 승인 여부를 결정하는 서브루틴.
# -trialUsername : 로그인을 시도하는 사용자 이름
# -trialPassword : 로그인을 시도하는 사용자가 입력한 암호
# 로그인 승인하면 nonzero를 반환하고 승인 거부 시 zero를 반환한다.
sub authorizeMembership(@)
{
	my %args = @_;
	my $trialUsername = $args{"-trialUsername"};
	my $trialPassword = $args{"-trialPassword"};

	# 로그인 요청하는 사용자 이름이 미리 지정된 이름과 같은지 검사
	if ($trialUsername eq $authorizedUsername)
	{
		# 이름이 일치하면, 암호가 같은지 검사
		if ($trialPassword eq $authorizedPassword)
		{
			# 이름과 암호가 모두 일치하면 로그인 승인
			return 1;
		}
	}

	# 그렇지 않을 경우 로그인 거부
	return 0;
}

################################################################################
# 웹 페이지 헤더 영역
# 클라이언트의 쿠키로부터 전송된 사용자 이름 및 암호를 읽어온다.

$membershipUsername = undef;
$membershipPassword = undef;

# CGI 객체를 생성한다.
$objectCGI = new CGI;
# 본 페이지로 전달된 쿠키를 확인한다.
%objectCookies = fetch CGI::Cookie;

# 쿠키가 존재하면
# 로그인 유효성 검사 : 클라이언트의 쿠키가 본 페이지에 전달한 로그인 정보를 검증한다.
if (%objectCookies ne undef)
{
	# 쿠키 변수 중 membershipUsername이라는 데이터가 있는지 확인
	if ($objectCookies{"membershipUsername"} ne undef)
	{
		# membershipUsername이 존재하면, membershipPassword라는 데이터도 있는지 확인 
		if ($objectCookies{"membershipPassword"} ne undef)
		{
			# 쿠키 변수에서 얻은 사용자 이름과 암호로 로그인 상태가 유효한지 검증
			$membershipUsername = $objectCookies{"membershipUsername"}->value;
			$membershipPassword = $objectCookies{"membershipPassword"}->value;

			# membershipUsername과 membershipPassword가 모두 수신되면,
			# 앞서 정의된 authorizeMembership 메서드로 로그인 요청한다.
			$authorizeStatus = authorizeMembership
			(
				-trialUsername=>$membershipUsername,
				-trialPassword=>$membershipPassword
			);
		}
	}
}

# 로그인 요청이 승인되었다면
if ($authorizeStatus)
{
	# 클라이언트의 쿠키 유효기간을 현재 시점으로부터 12시간으로 연장한다.

	# membershipUsername이라 이름붙인 유효기간 12시간짜리 쿠키 변수에 
	# 사용자 이름을 기록한다.
	$objectUsername = new CGI::Cookie
	(
		-name=>"membershipUsername",
		-value=>$membershipUsername,
		-expires=>"+12h"
	);

	# membershipPassword이라 이름붙인 유효기간 12시간짜리 쿠키 변수에 
	# 사용자 암호를 기록한다.
	$objectPassword = new CGI::Cookie
	(
		-name=>"membershipPassword",
		-value=>$membershipPassword,
		-expires=>"+12h"
	);

	# 쿠키는 HTTP 헤더 부분에 명시되어야 하므로 웹 페이지의 본문에 앞서 이를 출력함
	print "Set-Cookie: ", $objectUsername->as_string, "\n";
	print "Set-Cookie: ", $objectPassword->as_string, "\n";
}

print "Content-type: text/html; charset=UTF-8\n\n";

################################################################################
# 웹 페이지 본문 영역

print "<!DOCTYPE html>";
print "<html lang='ko'>";
print "<head>";
print "<meta charset='UTF-8' />";
print "<title>3 단계 : 회원 페이지</title>";
print "</head>";
print "<body>";
print "<header>";
print "<h1>3 단계 : 회원 페이지</h1>";
print "</header>";
print "<hr />";
print "<section>";
if ($authorizeStatus)
{
	# 로그인 승인 되었다면...
	print "<p>회원 전용 페이지</p>";
	print "<p>환영합니다. $membershipUsername 님.</p>";
	print "<p>쿠키 문자열 :</p>";
	print "<pre><code>$ENV{'HTTP_COOKIE'}</code></pre>";
	print "<p><a href='./step4.cgi'>로그아웃</a></p>";
}
else
{
	# 로그인 거부 되었다면...
	print "<p>이 페이지를 보려면 로그인이 필요합니다.</p>";
	print "<p><a href='./step1.cgi'>로그인</a></p>";
}
print "</section>";
print "<hr />";
print "<footer>";
print "<p>";
print "<span><a href='./step1.cgi'>1 단계(로그인)</a> | </span>";
print "<span><a href='./step2.cgi'>2 단계(사용자 인증)</a> | </span>";
print "<span><strong>3 단계(회원 페이지)</strong> | </span>";
print "<span><a href='./step4.cgi'>4 단계(로그아웃)</a></span>";
print "</p>";
print "</footer>";
print "</body>";
print "</html>";

[그림 5] step3.cgi 소스 코드의 실행 화면 (로그인 성공)

[그림 6] step3.cgi 소스 코드의 실행 화면 (로그인 실패)

위 소스 코드는 클라이언트 측 쿠키에 보관된 쿠키 변수 중 membershipUsernamemembershipPassword를 읽어와서 로그인 유효성 검사를 수행하고, 로그인 검증이 끝나면 쿠키의 유효기간이 연장되어 있음을 확인할 수 있다.

[그림 7] step3.cgi 소스 코드의 실행 결과 유효기간이 연장된 쿠키

step4.cgi - 로그아웃 화면


로그인 상태임이 확인되면 클라이언트의 쿠키를 만료시켜서 로그아웃 작업을 수행한다. 그렇지 않은 경우 이미 로그아웃 상태임을 알려준다.

#!/usr/bin/perl

use CGI qw/:standard/;
use CGI::Cookie;

################################################################################
# 사용자 인증 로직
# 이름과 암호를 비교하며 승인되면 nonzero, 승인 안 되면 zero를 반환한다.

# 아래 이름의 사용자면 로그인 승인 함.
my $authorizedUsername = "codingCat";
# 아래의 암호를 입력하면 로그인 승인 함.
my $authorizedPassword = "qwerty123456!";

# 로그인 승인 시 nonzero, 아니면 zero
my $authorizeStatus = 0;

# authorizeMembership(-trialUsername, -trialPassword)
# 사용자 이름과 암호를 비교하여 로그인 승인 여부를 결정하는 서브루틴.
# -trialUsername : 로그인을 시도하는 사용자 이름
# -trialPassword : 로그인을 시도하는 사용자가 입력한 암호
# 로그인 승인하면 nonzero를 반환하고 승인 거부 시 zero를 반환한다.
sub authorizeMembership(@)
{
	my %args = @_;
	my $trialUsername = $args{"-trialUsername"};
	my $trialPassword = $args{"-trialPassword"};

	# 로그인 요청하는 사용자 이름이 미리 지정된 이름과 같은지 검사
	if ($trialUsername eq $authorizedUsername)
	{
		# 이름이 일치하면, 암호가 같은지 검사
		if ($trialPassword eq $authorizedPassword)
		{
			# 이름과 암호가 모두 일치하면 로그인 승인
			return 1;
		}
	}

	# 그렇지 않을 경우 로그인 거부
	return 0;
}

################################################################################
# 웹 페이지 헤더 영역
# 클라이언트의 쿠키로부터 전송된 사용자 이름 및 암호를 읽어온다.

$membershipUsername = undef;
$membershipPassword = undef;

# CGI 객체를 생성한다.
$objectCGI = new CGI;
# 본 페이지로 전달된 쿠키를 확인한다.
%objectCookies = fetch CGI::Cookie;

# 쿠키가 존재하면
# 로그인 유효성 검사 : 클라이언트의 쿠키가 본 페이지에 전달한 로그인 정보를 검증한다.
if (%objectCookies ne undef)
{
	# 쿠키 변수 중 membershipUsername이라는 데이터가 있는지 확인
	if ($objectCookies{"membershipUsername"} ne undef)
	{
		# membershipUsername이 존재하면, membershipPassword라는 데이터도 있는지 확인
		if ($objectCookies{"membershipPassword"} ne undef)
		{
			# 쿠키 변수에서 얻은 사용자 이름과 암호로 로그인 상태가 유효한지 검증
			$membershipUsername = $objectCookies{"membershipUsername"}->value;
			$membershipPassword = $objectCookies{"membershipPassword"}->value;

			# membershipUsername과 membershipPassword가 모두 수신되면,
			# 앞서 정의된 authorizeMembership 메서드로 로그인 요청한다.
			$authorizeStatus = authorizeMembership
			(
				-trialUsername=>$membershipUsername,
				-trialPassword=>$membershipPassword
			);
		}
	}
}

# 로그아웃은 로그인 여부와 무관하게 수행된다.
# 클라이언트의 쿠키 유효기간을 현재 시점으로부터 12시간 이전으로 설정한다.

# membershipUsername이라 이름붙인 유효기간 만료된 쿠키변수에 
# 사용자 이름을 기록한다.
$objectUsername = new CGI::Cookie
(
	-name=>"membershipUsername",
	-value=>"<undefined>",
	-expires=>"-12h"
);

# membershipPassword이라 이름붙인 유효기간 만료된 쿠키변수에 
# 사용자 암호를 기록한다.
$objectPassword = new CGI::Cookie
(
	-name=>"membershipPassword",
	-value=>"<undefined>",
	-expires=>"-12h"
);

# 쿠키는 HTTP 헤더 부분에 명시되어야 하므로 웹 페이지의 본문에 앞서 이를 출력함
print "Set-Cookie: ", $objectUsername->as_string, "\n";
print "Set-Cookie: ", $objectPassword->as_string, "\n";

print "Content-type: text/html; charset=UTF-8\n\n";

################################################################################
# 웹 페이지 본문 영역

print "<!DOCTYPE html>";
print "<html lang='ko'>";
print "<head>";
print "<meta charset='UTF-8' />";
print "<title>4 단계 : 로그아웃</title>";
print "<style>a { color: #0000FF; }</style>";
print "</head>";
print "<body>";
print "<header>";
print "<h1>4 단계 : 로그아웃</h1>";
print "</header>";
print "<hr />";
print "<section>";
if ($authorizeStatus)
{
	# 로그인 된 상태였다면...
	print "<p>로그아웃되었습니다.</p>";
	print "<p><a href='./step1.cgi'>로그인</a></p>";
}
else
{
	# 로그인 안 된 상태였다면...
	print "<p>로그인되어있지 않습니다.</p>";
	print "<p><a href='./step1.cgi'>로그인</a></p>";
}
print "</section>";
print "<hr />";
print "<footer>";
print "<p>";
print "<a href='./step1.cgi'>1 단계(로그인)</a> |";
print "<a href='./step2.cgi'>2 단계(사용자 인증)</a> | ";
print "<a href='./step3.cgi'>3 단계(회원 페이지)</a> | ";
print "<strong>4 단계(로그아웃)</string>";
print "</p>";
print "</footer>";
print "</body>";
print "</html>";

[그림 8] step4.cgi 소스 코드의 실행 화면 (로그아웃 성공)

[그림 9] step4.cgi 소스 코드의 실행 화면 (이미 로그아웃된 상태)

로그아웃은 쿠키에서 해당 변수(membershipUsername, membershipPassword)를 삭제하는 과정인데 그 방법은 유효기간을 과거로 설정하여 클라이언트에게 덮어쓰도록 전송하는 것이다. 본 소스코드에서는 재차 로그인 할 때 발생할 수 있는 오류를 방지하고자, 로그인 상태와 관계 없이 두 쿠키 변수를 삭제하도록 구성해보았다. 로그인 상태를 나타내는 변수($authorizeStatus)는 단순히 안내 메시지의 선택을 위해서만 사용되었다. step4.cgi의 로그아웃 결과, 클라이언트에서 두 개의 쿠키변수가 사라졌음을 다음과 같이 확인할 수 있다.

[그림 10] step4.cgi 소스 코드의 실행 결과 쿠키가 삭제된 상태

step1.cgi - 중복 로그인을 방지하기 위한 소스 코드의 수정


앞서 작성한 step3.cgi을 살펴보면, 쿠키로부터 사용자 이름과 암호를 읽은 후 로그인 유효성을 재검증하는 부분이 있다. 로그인이 재차 승인되면 쿠키의 유효기간이 연장되면서 회원 전용 페이지를 보여주고 그렇지 않으면 로그인 화면으로 안내하는 메시지를 보여주는데 이를 step1.cgi에 적용해본다. 중복 로그인을 방지하기 위해 쿠키를 읽어들이고 이미 로그인이 유효한 상태라면 곧바로 회원 페이지로 넘어갈 수 있도록 안내하는 기능이 추가된다.

#!/usr/bin/perl

use CGI qw/:standard/;
use CGI::Cookie;

################################################################################
# 사용자 인증 로직
# 이름과 암호를 비교하며 승인되면 nonzero, 승인 안 되면 zero를 반환한다.

# 아래 이름의 사용자면 로그인 승인 함.
my $authorizedUsername = "codingCat";
# 아래의 암호를 입력하면 로그인 승인 함.
my $authorizedPassword = "qwerty123456!";

# 로그인 승인 시 nonzero, 아니면 zero
my $authorizeStatus = 0;

# authorizeMembership(-trialUsername, -trialPassword)
# 사용자 이름과 암호를 비교하여 로그인 승인 여부를 결정하는 서브루틴.
# -trialUsername : 로그인을 시도하는 사용자 이름
# -trialPassword : 로그인을 시도하는 사용자가 입력한 암호
# 로그인 승인하면 nonzero를 반환하고 승인 거부 시 zero를 반환한다.
sub authorizeMembership(@)
{
	my %args = @_;
	my $trialUsername = $args{"-trialUsername"};
	my $trialPassword = $args{"-trialPassword"};

	# 로그인 요청하는 사용자 이름이 미리 지정된 이름과 같은지 검사
	if ($trialUsername eq $authorizedUsername)
	{
		# 이름이 일치하면, 암호가 같은지 검사
		if ($trialPassword eq $authorizedPassword)
		{
			# 이름과 암호가 모두 일치하면 로그인 승인
			return 1;
		}
	}

	# 그렇지 않을 경우 로그인 거부
	return 0;
}

################################################################################
# 웹 페이지 헤더 영역
# 세션에 기록된 사용자 이름 및 암호를 읽어와서 로그인 유효성을 검증한다.

$membershipUsername = undef;
$membershipPassword = undef;

# CGI 객체를 생성한다.
$objectCGI = new CGI;
# 본 페이지로 전달된 쿠키를 확인한다.
%objectCookies = fetch CGI::Cookie;

# 쿠키가 존재하면
# 로그인 유효성 검사 : 클라이언트의 쿠키가 본 페이지에 전달한 로그인 정보를 검증한다.
if (%objectCookies ne undef)
{
	# 쿠키 변수 중 membershipUsername이라는 데이터가 있는지 확인
	if ($objectCookies{"membershipUsername"} ne undef)
	{
		# membershipUsername이 존재하면, membershipPassword라는 데이터도 있는지 확인 
		if ($objectCookies{"membershipPassword"} ne undef)
		{
			# 쿠키 변수에서 얻은 사용자 이름과 암호로 로그인 상태가 유효한지 검증
			$membershipUsername = $objectCookies{"membershipUsername"}->value;
			$membershipPassword = $objectCookies{"membershipPassword"}->value;
			
			# membershipUsername과 membershipPassword가 모두 수신되면,
			# 앞서 정의된 authorizeMembership 메서드로 로그인 요청한다.
			$authorizeStatus = authorizeMembership
			(
				-trialUsername=>$membershipUsername,
				-trialPassword=>$membershipPassword
			);
		}
	}
}

# 로그인 요청이 승인되었다면
if ($authorizeStatus)
{
	# 클라이언트의 쿠키 유효기간을 현재 시점으로부터 12시간으로 연장한다.

	# membershipUsername이라 이름붙인 유효기간 12시간짜리 쿠키 변수에 
	# 사용자 이름을 기록한다.
	$objectUsername = new CGI::Cookie
	(
		-name=>"membershipUsername",
		-value=>$membershipUsername,
		-expires=>"+12h"
	);

	# membershipPassword이라 이름붙인 유효기간 12시간짜리 쿠키 변수에 
	# 사용자 암호를 기록한다.
	$objectPassword = new CGI::Cookie
	(
		-name=>"membershipPassword",
		-value=>$membershipPassword,
		-expires=>"+12h"
	);

	# 쿠키는 HTTP 헤더 부분에 명시되어야 하므로 웹 페이지의 본문에 앞서 이를 출력함
	print "Set-Cookie: ", $objectUsername->as_string, "\n";
	print "Set-Cookie: ", $objectPassword->as_string, "\n";
}

print "Content-type: text/html; charset=UTF-8\n\n";

################################################################################
# 웹 페이지 본문 영역

print "<!DOCTYPE html>";
print "<html lang='ko'>";
print "<head>";
print "<meta charset='UTF-8' />";
print "<title>1 단계 : 로그인</title>";
print "<style>a { color: #0000FF; }</style>";
print "</head>";
print "<body>";
print "<header>";
print "<h1>1 단계 : 로그인</h1>";
print "</header>";
print "<hr />";
print "<section>";
if ($authorizeStatus)
{
	# 로그인 승인 되었다면...
	print "<p>이미 로그인되어 있습니다.</p>";
	print "<p><a href='step3.cgi'>회원 페이지</a></p>";
}
else
{
	# 로그인 거부 되었다면...
	print "<p>사용자 이름과 사용자 암호를 입력하세요.</p>";
	print "<form action='./step2.cgi' method='post'>";
	print "<label for='trialUsername'>사용자 이름 : </label><input type='text' id='trialUsername' name='trialUsername' />";
	print "<label for='trialPassword'>사용자 암호 : </label><input type='password' id='trialPassword' name='trialPassword' />";
	print "<input type='submit' />";
	print "</form>";
}
print "</section>";
print "<hr />";
print "<footer>";
print "<p>";
print "<span><strong>1 단계(로그인)</strong> | </span>";
print "<span><a href='./step2.cgi'>2 단계(사용자 인증)</a> | </span>";
print "<span><a href='./step3.cgi'>3 단계(회원 페이지)</a> | </span>";
print "<span><a href='./step4.cgi'>4 단계(로그아웃)</a></span>";
print "</p>";
print "</footer>";
print "</body>";
print "</html>";

[그림 11] 로그인이 유효한 상태에서 접속하였을 때, step1.cgi 소스 코드의 출력 결과
카테고리 “Common Gateway Interface/Perl”
more...
썸네일 이미지
libc 문자열 조작 함수 정리 (part 11 - strerror) [完]
libc 문자열 조작 함수 정리 C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로..
Language/C & C++
2018. 8. 23. 09:08

libc 문자열 조작 함수 정리 (part 11 - strerror) [完]

Language/C & C++
2018. 8. 23. 09:08

libc 문자열 조작 함수 정리


C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로 하여 작성되었다.

  1. libc 문자열 조작 함수 정리 (part 01 - strcpy, strncpy)
  2. libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)
  3. libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)
  4. libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)
  5. libc 문자열 조작 함수 정리 (part 05 - strstr)
  6. libc 문자열 조작 함수 정리 (part 06 - strtok)
  7. libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)
  8. libc 문자열 조작 함수 정리 (part 08 - strlen)
  9. libc 문자열 조작 함수 정리 (part 09 - strpbrk)
  10. libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)
  11. libc 문자열 조작 함수 정리 (part 11 - strerror)

Part XI. strerror


이번 포스팅에서는 가장 최근에 발생한 오류 내용을 확인할 때 사용하는 함수인 strerror 함수에 대해 정리한다.

1. strerror


strerror 함수의 원형은 다음과 같이 정의되어 있다.

char * strerror(int errnum);
errnum
오류 번호이다. 이 값에 따라 각기 다른 오류 내용을 볼 수 있다. 가장 최근에 발생한 오류의 고유번호를 알고자 한다면 errno.h 헤더에 정의되어 있는 errno 전역 식별자를 여기에 전달하면 된다.

다음은 strerror 함수의 사용 예이다.

/* strerror.c */
#include <stdio.h>
#include <string.h>
#include <errno.h>                                         // identifier 'errno'

int main(int argc, char * argv[])
{
	FILE * fp = NULL;
	
	if ((fp = fopen("NotExists", "r")) != NULL)
	{
		printf("File Exists.\n");                         // File Exists
		fclose(fp); 
	}
	else
	{
		printf("File NOT Exists.\n");                      // File Error
		printf("\"%s\"\n", strerror(errno));
	}

	return 0;
}
[그림 1] strerror.c 예제 소스 코드
[그림 2] strerror.c 예제 소스 코드의 실행 결과

존재 하지 않는 이름("NotExists")의 파일을 fopen 함수를 통해 열려고 하였다. 당연히 fopen 함수는 NULL을 반환하지만 한편으로는 전역변수 errno에 오류 코드를 설정한다. 이 오류 코드는 정수(int)형인데 구체적으로 사용자가 읽을 수 있는 텍스트로써 어떤 내용인지를 보고자 할 때 strerror 함수를 사용하여 문자열 형태로 출력하고 있음을 확인할 수 있다.

1-1. Wide Character 확장 함수 - _wcserror


현재 버전의 표준 C 라이브러리에는 wchar 버전의 strerror 함수가 정의되어 있지 않다. Visual Studio 사용자는 UTF-16/UTF-32와 같은 Wide Character 문자열 형식에 대해 위하여 다음의 함수를 사용 가능하다.

wchar_t * _wcserror(int errnum);

<Epilogue>


본 포스팅을 통해 문자열 비교 함수에 대해 정리해 보았다. 이것으로 C 표준 라이브러리(libc)에서 제공하는 문자열 조작 함수(strXXX)에 대한 정리를 모두 마친다.

- 끝 -

카테고리 “Language/C & C++”
more...
썸네일 이미지
libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)
libc 문자열 조작 함수 정리 C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로..
Language/C & C++
2018. 8. 21. 23:41

libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)

Language/C & C++
2018. 8. 21. 23:41

libc 문자열 조작 함수 정리


C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로 하여 작성되었다.

  1. libc 문자열 조작 함수 정리 (part 01 - strcpy, strncpy)
  2. libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)
  3. libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)
  4. libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)
  5. libc 문자열 조작 함수 정리 (part 05 - strstr)
  6. libc 문자열 조작 함수 정리 (part 06 - strtok)
  7. libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)
  8. libc 문자열 조작 함수 정리 (part 08 - strlen)
  9. libc 문자열 조작 함수 정리 (part 09 - strpbrk)
  10. libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)
  11. libc 문자열 조작 함수 정리 (part 11 - strerror)

Part X. strxfrm, strcoll


이번 포스팅에서는 시스템 로케일 설정에 따라 문자열을 변환하고 비교할 목적으로 사용되는 함수인 strxfrmstrcoll 함수에 대해 정리한다.

1. strxfrm


strxfrm 함수는 시스템 로케일 설정에 따라 문자열을 변환transform하여 버퍼에 복사하고 변환된 문자열의 길이를 반환하는 함수이다. 여기서 변환이란 시스템 로케일에서 정의한 문자열 변환작업을 의미하는데 구체적으로 무엇을 어떻게 변환하는지에 대해서는 명확하게 정의된 것이 없다. 다만, 변환된 문자열은 일종의 해시hash로서 취급되며 strcmp 함수에 의한 일치 또는 순서가 본래의 문자열과 일치함은 보장한다.

strxfrm 함수의 원형은 다음과 같다.

size_t strxfrm(char * destination, const char * source, size_t num);
destination
변환된 문자열이 복사될 문자열 버퍼이다.
source
변환할 문자열이 보관된 상수 또는 문자열 버퍼이다.
num
destination 버퍼가 보관 가능한 최대 문자 수이다.

함수의 수행 결과 현재 시스템 로케일 조건에서 원본 문자열로부터 변환된 문자열이 destination으로 지정한 문자열 버퍼에 복사되고 이 버퍼에 복사된 문자 수가 반환된다.

다음은 strxfrm 함수의 사용 예이다. macOS(10.13.6 High Sierra 기준) 및 FreeBSD에서는 로케일 기능을 구현하는 부분에서 버그가 존재하기 때문에 아래 소스 코드에 의한 결과가 다를 수 있다. 그 결과는 신뢰할 수 없으므로 가급적 macOSFreeBSD를 제외한 운영체제에서 실행해보기를 권장한다.

/* strxfrm.c */
#include <stdio.h>
#include <string.h>
#include <locale.h>

int main(int argc, char * argv[])
{
    char str1[512] = "hlava";
    char str2[512] = "číšník";
    char xfm1[512] = { '\0', };
    char xfm2[512] = { '\0', };
    char * result = NULL;
    size_t lxfm1 = 0;
    size_t lxfm2 = 0;

    lxfm1 = strxfrm(xfm1, str1, sizeof xfm1);
    lxfm2 = strxfrm(xfm2, str2, sizeof xfm2);
    if ((lxfm1 > 0) && (lxfm2 > 0))
    {
        printf("<Locale Unset>\n");
        printf("setlocale = \"%s\"\n", (result == NULL) ? "NULL" : result);
        printf("str1: \"%s\" --> \"%s\"\n", str1, xfm1);
        printf("str2: \"%s\" --> \"%s\"\n", str2, xfm2);
        printf("strcmp(str1, str2) = %d\n", strcmp(str1, str2));
        printf("strcmp(xfm1, xfm2) = %d\n", strcmp(xfm1, xfm2));
        printf("returns of strxfrm: %zu / %zu\n", lxfm1, lxfm2);
    }

    result = setlocale(LC_ALL, "cs_CZ.UTF-8");
    lxfm1 = strxfrm(xfm1, str1, sizeof xfm1);
    lxfm2 = strxfrm(xfm2, str2, sizeof xfm2);
    if ((lxfm1 > 0) && (lxfm2 > 0))
    {
        printf("<cs-CZ.UTF-8>\n");
        printf("setlocale = \"%s\"\n", result);
        printf("str1: \"%s\" --> \"%s\"\n", str1, xfm1);
        printf("str2: \"%s\" --> \"%s\"\n", str2, xfm2);
        printf("strcmp(str1, str2) = %d\n", strcmp(str1, str2));
        printf("strcmp(xfm1, xfm2) = %d\n", strcmp(xfm1, xfm2));
        printf("returns of strxfrm: %zu / %zu\n", lxfm1, lxfm2);
    }

    return 0;
}
[그림 1] strxfrm.c 예제 소스 코드
[그림 2] strxfrm.c 예제 소스 코드의 실행 결과

위 코드는 체코어 단어인 "hlava""číšník"의 순서를 비교하는 예이다. 두 단어는 각각 str1str2에 UTF-8 인코딩으로 보관되어 있다. (단, unix 일때에 한함.) Microsoft Windows 등의 운영체제를 고려하여 확실하게 UTF-8 인코딩으로 문자열 상수를 보관하기 위해 str1str2의 상수 할당을 다음과 같이 지정해도 좋다.

/* strxfrm.c: alternate */
char str1[] = { 0x68, 0x6C, 0x61, 0x76, 0x61, 0x00 }; // hlava
char str2[] = { 0xC4, 0x8D, 0xC3, 0xAD, 0xC5, 0xA1, 0x6E, 0xC3, 0xAD, x6B, 0x00 }; // číšník

보통의 소문자 'c'의 유니코드는 U+0063이고, 카론caron이 붙은 소문자 'č'의 유니코드는 U+010D이다. 또한 보통의 소문자 'h'의 유니코드는 U+0068이다. 유니코드에 의한 단순 정렬 시,

'c' (U+0063) - 'h' (U+0068) - 'č' (U+010D)

가 되겠지만 체코어 알파벳의 순서대로 문자를 정렬할 경우,

'c' (U+0063) - 'č' (U+010D) - 'h' (U+0068)

의 순서로 정렬된다. strxfrm은 특정 언어로 적힌 문자열을 strcmp로 순서 비교할 때 이러한 문화권(로케일) 차이를 반영하여 해당 언어의 사전 순서대로 정렬할 수 있도록 특별한 패턴의 문자열을 생성하는 역할을 한다. 다시 말하면, strcmp로 순서 비교하고자 할 때 여기에 들어갈 문자열은 "hlava""číšník" 등의 원본 문자열이 아니고, strxfrm을 통해 변환된 문자열(일종의 해시hash)이어야 한다는 것이다.

첫 번째 실험인 <Locale Unset>항목을 본다.

아직 로케일을 명시하지 않은 상태이기 때문에 모든 문자열은 단순히 유니코드에 등재된 순서대로 비교 연산을 수행한다. 그렇기 때문에 strxfrm 함수는 문자열 버퍼에 원본 문자열 그대로를 복사하고 strcmp 함수는 원본 문자열 그대로 비교 연산을 수행한다. 앞서 설명한 대로 유니코드에 의한 단순 정렬 시 보통의 라틴문자인 'h'가 확장 라틴문자인 'č'에 선행하기 때문에(우선하기 때문에) strcmp("hlava", "číšník");의 결과 음수가 반환된다. strcmp의 반환값에 대한 설명은 [libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)]를 참고한다.

두 번째 실험인 <cs_CZ.UTF-8>항목을 본다.

setlocale 함수를 사용하여 로케일이 설정된 상태이므로 체코어 알파벳 순서에 따라 문자열의 비교가 가능하다. 로케일이 설정된 상태에서 strxfrm 함수는 문자열 버퍼에 변환된 문자열을 만든다. 각 문자열은 "hlava""číšník"로부터 얻어진 일종의 해시이기 때문에 읽을 수 있는 문자열은 아니지만 해당 문자열을 대신하여 strcmp 함수에 의한 우선순위 또는 일치 여부를 확인하는데 사용될 수 있다.

위와 같이 얻어진 문자열을 strcmp 함수에 적용해 보자. 로케일을 설정하기 전에는 'h''č'에 선행한다고 보아 음수를 반환하였는데, 로케일을 설정한 후에는 'č''h'에 선행한다고 보아서 양수를 반환하는 것을 볼 수 있다.

좀 더 확인하기 위하여 여러 종류의 로케일에 대해 문자열을 비교해보도록 한다. 위의 코드 중 setlocale에 전달되는 "cz_CS.UTF-8" 부분을 "문자열이 UTF-8로 인코드된 미국 영어 로케일"(en_US.UTF-8)과 "문자열이 UTF-8로 인코드된 한국어 로케일"(ko_KR.UTF-8)로 설정하였을 때 결과는 각각 다음과 같이 나올 것이다. (Ubuntu 기준)

########## terminal ##########
<ko_KR.UTF-8>
setlocale = "ko_KR.UTF-8"
str1: "hlava" --> "hlava"
str2: "číšník" --> "číšník"
strcmp(str1, str2) = -92
strcmp(xfm1, xfm2) = -92
returns of strxfrm: 5 / 10

<en_US.UTF-8>
setlocale = "en_US.UTF-8"
str1: "hlava" --> (garbage)
str2: "číšník" --> (garbage)
strcmp(str1, str2) = -92
strcmp(xfm1, xfm2) = 5
returns of strxfrm: 17 / 20

미국 영어 로케일(en_US)도 체코어와 같은 로마자를 사용하므로 문자 'č''c' 계열의 문자로 간주하여 'h'보다는 앞 순서로 판별하도록 strxfrm에서 문자열 변환이 이루어진다. 그러므로 strcmp(strxfrm("hlava"), strxfrm("číšník"));의 결과 양수가 반환된다.

한국어 로케일(ko_KR) 조건에서는 로마자 문화권이 아닌 상태가 되므로 'č' 문자를 인식하지 않아 단순 유니코드 값대로 순서가 판단되도록 strxfrm에서 문자열 변환이 일어나지 않는다. 그러므로 strcmp(strxfrm("hlava"), strxfrm("číšník"));의 결과 음수가 반환됨을 확인할 수 있다.

참고로 유닉스(리눅스)에서 현재 시스템이 지원 가능한 로케일의 목록을 확인하는 방법은 다음과 같다.

$ locale -a

로케일 관련된 파일들은 대체로 /usr/share/locale 디렉터리에 정의되어 있다. Debian(Ubuntu) 계열의 운영체제에서 특정 로케일을 설치하고자 할 경우 다음과 같이 실행한다.

예를 들어 한국어 로케일(ko-KR)을 생성하고자 할 경우,

$ sudo locale-gen ko_KR

그리고 EUC-KR 인코딩을 지원하는 한국어 로케일을 추가하고자 할 경우,

$ sudo locale-gen ko_KR.EUC-KR

1-1. Wide Character 확장 함수 - wcsxfrm


상기 strxfrm는 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 복사는 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

size_t wcsxfrm(wchar_t * destination, const wchar_t * source, size_t num);

2. strcoll


함수의 원형은 다음과 같이 정의되어 있다.

int strcoll(const char * str1, const char * str2);
str1
현지 언어로 적힌 첫번째 문자열이다.
str2
현지 언어로 적힌 두번째 문자열이다.

strcoll 함수는 strxfrm 함수와 strcmp 함수를 합친 함수로서, 현재 설정된 로케일에 의한 문자열 비교 연산을 수행한다. 즉 변환 문자열을 담기 위한 버퍼의 선언과 변환 문자열의 복사는 함수 내부적으로 알아서 수행되므로 strcmp 함수를 사용할 때와 같은 방식으로 원본 문자열만 직접 전달해주면 비교 연산 결과를 반환한다. 문자열 비교 연산의 결과에 대한 정리는 [libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)]를 참고한다.

앞서 예시로 적은 소스 코드를 strcoll 함수를 사용할 경우 다음과 같이 코드 분량을 줄이면서 현지 언어 문자열 비교를 할 수 있다.

/* strcoll.c */
#include <stdio.h>
#include <string.h>
#include <locale.h>

int main(int argc, char * argv[])
{
    char str1[512] = "hlava";
    char str2[512] = "číšník";
    char * result = NULL;

    printf("<Locale Unset>\n");
    printf("setlocale = \"%s\"\n", (result == NULL) ? "NULL" : result);
    printf("strcoll(\"%s\", \"%s\") = %d\n", str1, str2, strcoll(str1, str2));

    printf("\n");

    result = setlocale(LC_ALL, "cs_CZ.UTF-8");
    printf("<cs-CZ.UTF-8>\n");
    printf("setlocale = \"%s\"\n", (result == NULL) ? "NULL" : result);
    printf("strcoll(\"%s\", \"%s\") = %d\n", str1, str2, strcoll(str1, str2));

    return 0;
}
[그림 3] strcoll.c 예제 소스 코드
[그림 4] strcoll.c 예제 소스 코드의 실행 결과

마찬가지로 두 문자열에 대해 로케일을 적용하기 전의 문자열 비교 결과와 로케일 적용 후의 문자열 비교 결과가 서로 다름을 알 수 있다. 로케일을 적용하기 전에는 문자열을 단순 유니코드 순으로 정렬하므로 "hlava""číšník"에 선행한다고 보아 음수를 반환하지만, 로케일을 적용한 후에는 확장 라틴 문자 또한 해당 언어의 알파벳 순서대로 비교하므로 "číšník""hlava"에 선행한다고 보아 strcoll("hlava", "číšník");는 양수를 반환한다.

2-1. Wide Character 확장 함수 - wcscoll


상기 strcoll은 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 복사는 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

int wcscoll(const wchar_t * wcs1, const wchar_t * wcs2);

<Epilogue>


본 포스팅을 통해 문자열 비교 함수에 대해 정리해 보았다. 다음 포스팅[libc 문자열 조작 함수 정리 (part 11 - strerror)]에서는 가장 마지막에 발생한 오류의 내용을 구할 수 있는 strerror 함수에 대해 정리한다.

카테고리 “Language/C & C++”
more...
썸네일 이미지
libc 문자열 조작 함수 정리 (part 09 - strpbrk)
libc 문자열 조작 함수 정리 C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로..
Language/C & C++
2018. 8. 21. 15:57

libc 문자열 조작 함수 정리 (part 09 - strpbrk)

Language/C & C++
2018. 8. 21. 15:57

libc 문자열 조작 함수 정리


C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로 하여 작성되었다.

  1. libc 문자열 조작 함수 정리 (part 01 - strcpy, strncpy)
  2. libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)
  3. libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)
  4. libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)
  5. libc 문자열 조작 함수 정리 (part 05 - strstr)
  6. libc 문자열 조작 함수 정리 (part 06 - strtok)
  7. libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)
  8. libc 문자열 조작 함수 정리 (part 08 - strlen)
  9. libc 문자열 조작 함수 정리 (part 09 - strpbrk)
  10. libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)
  11. libc 문자열 조작 함수 정리 (part 11 - strerror)

Part IX. strpbrk


본 포스팅에서는 문자열에서 미리 열거된 문자들 중 하나를 검색pointer break하는 함수인 strpbrk 함수에 대해 정리한다.

1. strpbrk


C++ 라이브러리에서 제공하는 헤더 파일인 cstring에서는 검색 대상 문자열의 상수성 여부에 따라 2개의 함수가 오버로드overload되어 있다.

const char * strpbrk(const char * str1, const char * str2);
char * strpbrk(char * str1, const char * str2);

C 라이브러리에서 제공하는 헤더 파일인 string.h에서는 하나의 함수만이 정의되어 있다.

char * strpbrk(const char * str1, const char * str2);
str1
검색 대상 본문이 보관된 문자열 버퍼이다.
str2
검색할 문자들을 열거하는 문자열 상수이다.

다음은 문자열로부터 숫자의 개수를 세는 예이다.

/* strpbrk.c */
#include <stdio.h>
#include <string.h>

int main(int argc, char * argv[])
{
	char str1[512] =
		"L is for the way you look at me. "
		"O is for the only one I see. "
		"V is very, very extraordinary. "
		"E is even more than anyone that you adore.";
	char str2[] = "ELOV";
	char * result = NULL;

	printf("Original String: \n\"%s\"\n", str1);
	printf("****************************************\n");
	
	result = strpbrk(str1, str2);
	while (result != NULL)
	{
		printf("\"%s\"\n", result);
		result = strpbrk(result + 1, str2);
	}

	return 0;
}
[그림 1] strpbrk.c 예제 소스 코드
[그림 2] strpbrk.c 예제 소스 코드의 실행 결과

위 코드를 참조하면, 원본 문자열로부터 'E', 'L', 'O', 'V'로 시작하는 부분 문자열을 구하게 된다. 원본 문자열은 대문자로 시작하는 5개의 문장으로 구성되어 있고 이 문자열에 strpbrk를 적용하여 [그림 2]의 결과를 볼 수 있다. strpbrk의 두 번째 매개변수(str2)에는 'E', 'L', 'O', 'V'의 4개 문자만이 지정되어 있어 각 문자로 시작되는 부분 문자열을 얻을 수 있었지만, 'A'는 지정되지 않았으므로 마지막 문장은 출력되지 않음을 알 수 있다.

1-1. Wide Character 확장 함수 - wcspbrk


상기 strpbrk는 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 복사는 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

C++ 헤더인 cwchar에는 상수성 여부에 따라 다음의 두 함수가 오버로드되어 있다.

const wchar_t * wcspbrk(const wchar_t * wcs1, const wchar_t * wcs2);
wchar_t * wcspbrk(wchar_t * wcs1, const wchar_t * wcs2);

C 헤더인 wchar.h에는 하나의 함수만이 정의되어 있다.

wchar_t * wcspbrk(const wchar_t * str2, const wchar_t * str2);
카테고리 “Language/C & C++”
more...
썸네일 이미지
libc 문자열 조작 함수 정리 (part 08 - strlen)
libc 문자열 조작 함수 정리 C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로..
Language/C & C++
2018. 8. 21. 13:39

libc 문자열 조작 함수 정리 (part 08 - strlen)

Language/C & C++
2018. 8. 21. 13:39

libc 문자열 조작 함수 정리


C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로 하여 작성되었다.

  1. libc 문자열 조작 함수 정리 (part 01 - strcpy, strncpy)
  2. libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)
  3. libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)
  4. libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)
  5. libc 문자열 조작 함수 정리 (part 05 - strstr)
  6. libc 문자열 조작 함수 정리 (part 06 - strtok)
  7. libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)
  8. libc 문자열 조작 함수 정리 (part 08 - strlen)
  9. libc 문자열 조작 함수 정리 (part 09 - strpbrk)
  10. libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)
  11. libc 문자열 조작 함수 정리 (part 11 - strerror)

Part IIX. strlen


본 포스팅에서는 문자열의 길이length을 구하는 함수인 strlen 함수에 대해 정리한다.

<Prologue>


strlen은 C 스타일 문자열(맨 끝에 NULL ('\0')이 붙는 문자열)의 길이를 구하는 함수이다. 즉 NULL ('\0') 문자 직전의 문자까지만 센다. 예를 들어, 문자열 "Hello" = {'H', 'e', 'l', 'l', 'o', '\0'}의 경우 NULL ('\0') 문자를 제외한 5글자('H', 'e', 'l', 'l', 'o')만을 센다.

1. strlen


strlen의 원형은 다음과 같다.

size_t strlen(const char * str);

NULL문자를 만날때까지 문자를 하나씩 센다. 함수가 종료될 때 이 문자 수를 반환한다. 이 때 문자열 버퍼를 구성하는 NULL 문자는 개수에 포함되지 않는다. 메모리를 할당할 때 이를 고려하여 버퍼의 크기를 1개 문자 더 많게 설정해야 할 것이다.

다음은 strlen으로 문자열 버퍼에서 문자의 수를 세는 예시이다.

/* strlen.c */
#include <stdio.h>
#include <string.h>

int main(int argc, char * argv[])
{
	char str[64] = "Hello, World!";
	size_t length = 0;

	length = strlen(str);

	printf("original text: \"%s\" (%zu characters).\n", str, length);

	return 0;
}
[그림 1] strlen.c 예제 소스 코드
[그림 2] strlen.c 예제 소스 코드의 실행 결과

2-1. Wide Character 확장 함수 - wcslen


상기 strlen는 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 경우 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

size_t wcslen(const wchar_t * wcs);

<Epilogue>


본 포스팅을 통해 문자열 복사 함수에 대해 정리해 보았다. 다음 포스팅[libc 문자열 조작 함수 정리 (part 09 - strpbrk)]에서는 특정 문자의 위치를 반환하는 함수인 strpbrk에 대해 정리한다.

카테고리 “Language/C & C++”
more...
썸네일 이미지
libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)
libc 문자열 조작 함수 정리 C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로..
Language/C & C++
2018. 8. 20. 20:53

libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)

Language/C & C++
2018. 8. 20. 20:53

libc 문자열 조작 함수 정리


C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로 하여 작성되었다.

  1. libc 문자열 조작 함수 정리 (part 01 - strcpy, strncpy)
  2. libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)
  3. libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)
  4. libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)
  5. libc 문자열 조작 함수 정리 (part 05 - strstr)
  6. libc 문자열 조작 함수 정리 (part 06 - strtok)
  7. libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)
  8. libc 문자열 조작 함수 정리 (part 08 - strlen)
  9. libc 문자열 조작 함수 정리 (part 09 - strpbrk)
  10. libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)
  11. libc 문자열 조작 함수 정리 (part 11 - strerror)

Part VII. strspn, strcspn


본 포스팅에서는 전체 문자열 중에서 특정 범위의 문자로만 구성된 부분 문자열span을 구하는 함수인 strspnstrcspn 함수에 대해 정리한다.

<Prologue>


strspn은 문자열을 첫 글자부터 끝 글자까지 하나씩 탐색해 나가다가 미리 지정한 문자 외의 다른 문자를 만나면 지금까지 센 문자수를 반환하고 종료하는 기능을 한다. 즉 특정 범위의 문자로만 구성된 부분 문자열의 길이를 얻는 기능을 수행한다. 반대로 strcspn는 문자열을 첫 글자부터 끝 글자까지 하나씩 탐색해 나가다가 특정 범위의 문자로만 구성된 부분 문자열을 만났을 경우 그 위치를 반환한다.

1. strspn

strspn의 원형은 다음과 같다.

size_t strspn(const char * str1, const char * str2);
str1
검색 대상이 되는 문자열이다.
str2
문자들을 열거하는 패턴pattern이다.

반환 값은 size_t 형으로서, 맨 처음 문자부터 시작하여 str2에서 지정한 패턴을 벗어나지 않는 부분 문자열의 길이를 반환한다. 패턴에 맞는 부분 문자열이 처음부터 없다면 0을 반환한다.

예를 들어, 문장의 첫 부분에 금액이 적혀있는 경우 이를 인식하여 따로 보관하고자 한다. 금액은 '0'부터 '9'까지의 숫자 문자와 '.', ',', $ 기호로 구성되어 있기 때문에 이 범위를 벗어난 부분 문자열이 나타날때까지 문장의 첫 글자부터 세어 나가게 한다.

/* strspn.c */
#include <stdio.h>
#include <string.h>

int main(int argc, char * argv[])
{
	char str1[64] = "$1,823.55 is paid to purchase this product.";
	char str2[64] = { '\0', };
	char pattern[64] = "1234567890$,.";
	size_t result = 0;

	printf("original text: %s\n", str1);

	result = strspn(str1, pattern);

	if (result > 0)
	{
		strncpy(str2, str1, result);
		printf("currency data found: \"%s\"\n", str2);
	}
	else
	{
		printf("currency data not found.\n");
	}

	return 0;
}
[그림 1] strspn.c 예제 소스 코드
[그림 2] strspn.c 예제 소스 코드의 실행 결과

위 코드를 참조하면, result = strspn(str1, pattern);에 의해 문자열의 맨 처음 문자로부터 주어진 패턴("123456789$,.")을 만족하는 부분 문자열의 길이를 얻는다. 이 값은 result 변수에 보관되며 구체적으로 9의 값("$1,823.55"의 총 9 문자)이 보관된다.

이 값은 strncpy 함수에서 사용된다. strncpy(str2, str1, result);에 의해 str1의 처음 9 문자가 str2에 복사된다. 즉, 해당 패턴을 만족하는 부분 문자열이 별도의 버퍼에 복사된 것이다. 이를 그 다음 줄인 printf 함수에서 출력하는 방식으로 본 예제는 작동한다.

1-1. Wide Character 확장 함수 - wcsspn


상기 strspn는 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 경우 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

size_t wcsspn(const wchar_t * str1, const wchar_t * str2);

2. strcspn


strcspn은 지정된 패턴을 만족하지 않는 부분 문자열의 길이를 구할 때 사용한다. 원형은 다음과 같다.

size_t strcspn(const char * str1, const char * str2);
str1
검색 대상이 되는 문자열이다.
str2
문자들을 열거하는 패턴pattern이다.

실행 결과 패턴을 만족하지 않는 부분 문자열의 길이, 다시 말하면 패턴을 만족하는 부분 문자열이 시작하는 위치를 반환한다. 만일 str1에는 str2에서 지정한 패턴에 맞는 부분 문자열이 없을 경우 str1의 길이를 반환한다.

상기 예제 코드에서 문자열을 수정하였다. 금액 관련한 표현이 문자열의 처음에 등장하지 않을 경우 해당 위치를 찾을 때 본 함수를 활용 가능하다.

/* strcspn.c */
#include <stdio.h>
#include <string.h>

int main(int argc, char * argv[])
{
	char str1[64] = "Totol expense: $1,234.56 will be paid.";
	char str2[64] = { '\0', };
	char pattern[64] = "1234567890$,.";
	size_t result = 0;
	size_t count = 0;

	printf("original text: \"%s\"\n", str1);

	result = strcspn(str1, pattern);
	if (result > 0)
	{
		printf("currency data found at %zu(th) position.\n", result);
		
		count = strspn((str1 + result), pattern);
		if (count > 0)
		{
			strncpy(str2, (str1 + result), count);
			printf("currency data found: \"%s\"\n", str2);
		}
		else
		{
			printf("unexpected error.\n");
		}
	}
	else
	{
		printf("currency data not found.\n");
	}
	return 0;
}
[그림 1] strcspn.c 예제 소스 코드
[그림 2] strcspn.c 예제 소스 코드의 실행 결과

위 소스 코드를 참조하면, 찾고자 하는 패턴("1234567890$,.")의 숫자 문자열이 문장의 중간에 위치해 있는데 정확히 몇 번째 문자부터 시작하는지를 확인하기 위하여 strcspn을 사용한다. 이 함수는 해당 패턴이 만족하지 않는 부분 문자열의 길이를 반환하는데 이는 곧 해당 패턴이 만족하는 영역의 시작 위치이므로, result 변수에 보관한다. 구체적으로 이 값은 15, 즉 15번째 문자부터 해당 패턴을 만족하는 부분 문자열이 존재함을 알린다. (주의: C 언어는 맨 처음 문자를 0 번째로 본다.)

if 문의 조건(result > 0)을 만족하므로 상기 설명한 strspn 함수를 사용하여 검출하고자 하는 부분 문자열의 길이를 구한다. 이 때 매개변수로 전달되는 문자열 포인터는 str1 = &str[0]이 아니라 (str1 + result) = &str[15]임을 확인한다. strspn은 문자열의 중간에서 부분 문자열을 복사하는 기능이 없으므로 strcspn에서 구한 위치와 원본 문자열을 포인터 연산하여 해당 부분 문자열이 strspn의 입장에서 가장 처음에 오도록 전달하는 것이다.

패턴에 맞는 부분 문자열의 시작 위치를 strcspn을 통해 알고 있고 그 부분 문자열의 길이를 strspn을 통해 알았다면 복사만 하면 된다. 부분 문자열의 복사는 strncpy로 수행하는데 역시 복사 대상 문자열은 (str1 + result) = &str[15]으로 전달되었음을 확인한다.

2-1. Wide Character 확장 함수 - wcscspn


상기 strcspn는 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 경우 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

size_t wcscspn(const wchar_t * str1, const wchar_t * str2);

<Epilogue>


본 포스팅을 통해 문자열 복사 함수에 대해 정리해 보았다. 다음 포스팅[libc 문자열 조작 함수 정리 (part 08 - strlen)]에서는 문자열의 길이를 구하는 함수인 strlen 함수에 대해 정리한다.

카테고리 “Language/C & C++”
more...
썸네일 이미지
libc 문자열 조작 함수 정리 (part 06 - strtok)
libc 문자열 조작 함수 정리 C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로..
Language/C & C++
2018. 8. 20. 19:14

libc 문자열 조작 함수 정리 (part 06 - strtok)

Language/C & C++
2018. 8. 20. 19:14

libc 문자열 조작 함수 정리


C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로 하여 작성되었다.

  1. libc 문자열 조작 함수 정리 (part 01 - strcpy, strncpy)
  2. libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)
  3. libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)
  4. libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)
  5. libc 문자열 조작 함수 정리 (part 05 - strstr)
  6. libc 문자열 조작 함수 정리 (part 06 - strtok)
  7. libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)
  8. libc 문자열 조작 함수 정리 (part 08 - strlen)
  9. libc 문자열 조작 함수 정리 (part 09 - strpbrk)
  10. libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)
  11. libc 문자열 조작 함수 정리 (part 11 - strerror)

Part VI. strtok


본 포스팅에서는 특정 기호를 기준으로 문자열 분할tokenization을 지원하는 함수인 strtok 계열의 함수에 대해 사용 예를 정리한다.

<Prologue>


strtok은 지정된 문자를 기준으로 문자열을 자르는 역할을 한다. 예를 들어 문자열 중에 ';' 문자가 있다면 그 문자 직전까지를 하나의 문자열로 보고, 그 문자 직후부터를 또 하나의 문자열로 취급한다는 뜻이다. 이 때 strtok 함수는 해당 문자열에서 ';' 문자가 있던 자리를 NULL 문자로 치환해버린다. 즉 원본 문자열의 내용이 변한다는 것이다.

구체적으로 "Rachel;Tom;John;Michael;Jude"라는 문자열에 대해 strtok 함수를 사용하여 ';' 문자를 기준으로 분할을 수행할 때 "Rachel", "Tom", "John", "Michael", "Jude"라는 5개의 부분 문자열을 얻는데, 이 과정에서 원본 문자열은 "Rachel\0Tom\0John\0Michael\0Jude"와 같이 원래 있던 ';' 문자가 NULL 문자로 변경된다.

1. strtok


strtok 함수의 원형은 다음과 같다.

char * strtok(char * str, const char * delimiters);
str
분할할 문자열이다. 내용이 변할 것이므로 const가 붙지 않는다. 즉 문자열 상수는 받을 수 없고 버퍼만 받을 수 있다.
deliminiters
분할의 기준이 되는 문자이다. 문자열로 입력받기 때문에 기준 문자를 여러개 지정할 수 있다. 그 중 하나라도 분할할 문자열에 있다면 그 문자를 NULL 문자로 고치면서 부분 문자열을 반환한다.

str의 문자열에서 deliminiters에 해당하는 문자가 발견되면 그 자리를 NULL 문자로 치환하고 부분 문자열을 반환한다. 문자열의 끝에 도달하였거나 분할할 것이 없다면 NULL을 반환한다.

/* strtok.c */
#include <stdio.h>
#include <string.h>

int main(int argc, char * argv[])
{
	char str1[64] = "name=Tom;social=facebook,twitter,instagram";
	char str2[64] = "=;,";
	char * result = NULL;
	char * temporary = NULL;

	printf("<BEFORE>\n");
	printf("str1 = \"%s\";\n", str1);

	printf("<STRTOK>\n");
	result = strtok(str1, str2);
	printf("strtok(str1, \"%s\") = \"%s\";\n", str2, result);
	while (result != NULL)
	{
		result = strtok(NULL, str2);

		if (result == NULL)
			printf("strtok(NULL, \"%s\") = NULL;\n", str2);
		else
			printf("strtok(NULL, \"%s\") = \"%s\";\n", str2, result);
	}

	printf("<AFTER>\n");
	printf("str1 = \"%s\";\n", str1);

	return 0;
}
[그림 1] strtok.c 예제 소스 코드
[그림 2] strstr.c 예제 소스 코드의 실행 결과

위 코드는 "name=Tom;social=facebook,twitter,instagram"의 문자열을 strtok 함수로 분할하는 예이다. 해당 문자열은 세미콜론(';')과 등호('=') 및 컴마(',')로 단어들을 분리해낼 수 있음을 직관적으로 알 수 있다. C 언어로 이를 분리하기 위하여 deliminiters 매개변수에 세 기호들이 열거된 문자열을 전달한다.

분리 대상이 되는 문자열은 result = strtok(str1, str2);로서 최초 1회만 매개변수로 전달되고 그 이후 호출에서는 result = strtok(NULL, str2);와 같이 전달하지 않는다. 이미 대산 문자열을 한 번 분할했기 때문에 strtok 함수는 내부적으로 가장 마지막으로 분할한 위치를 기억하고 있기 때문이다. 만일 함수 호출 실행시마다 첫 번째 매개변수에 대상 문자열을 전달하면 분할 작업을 가장 첫 문자부터 시작하므로 최초로 분할되어 나온 부분 문자열만 반복하여 얻어질 것이다.

검색할 문자열의 끝에 도달하였거나 문자열에 분할 기준의 문자가 없어서 더 이상 분할될 수 없을 경우에는 NULL을 반환한다. 루프를 수행중이었다면 이 시점에 루프를 종료하면 된다.

1-1. Wide Character 확장 함수 - wcstok


상기 strtok는 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 'tokenization'은 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

wchar_t * wcstok(wchar_t * wcs, const wchar_t * delimiters);

<Epilogue>


본 포스팅을 통해 문자열 복사 함수에 대해 정리해 보았다. 다음 포스팅[libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)]에서는 패턴에 맞는 부분 문자열을 검색하는 함수인 strspn, strcspn 함수에 대해 정리한다.

카테고리 “Language/C & C++”
more...
썸네일 이미지
libc 문자열 조작 함수 정리 (part 05 - strstr)
libc 문자열 조작 함수 정리 C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로..
Language/C & C++
2018. 8. 20. 18:06

libc 문자열 조작 함수 정리 (part 05 - strstr)

Language/C & C++
2018. 8. 20. 18:06

libc 문자열 조작 함수 정리


C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로 하여 작성되었다.

  1. libc 문자열 조작 함수 정리 (part 01 - strcpy, strncpy)
  2. libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)
  3. libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)
  4. libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)
  5. libc 문자열 조작 함수 정리 (part 05 - strstr)
  6. libc 문자열 조작 함수 정리 (part 06 - strtok)
  7. libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)
  8. libc 문자열 조작 함수 정리 (part 08 - strlen)
  9. libc 문자열 조작 함수 정리 (part 09 - strpbrk)
  10. libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)
  11. libc 문자열 조작 함수 정리 (part 11 - strerror)

Part V. strstr


본 포스팅에서는 부분 문자열substring 포함 여부 확인을 지원하는 함수인 strstr 계열의 함수에 대해 사용 예를 정리한다.

<Prologue>


strstr은 문자열에 대하여 지정한 부분 문자열을 포함하는 지 여부를 확인하는 함수이다. 가장 직접적인 예시로는 문장 내 비속어를 찾아 필터링을 하는 경우가 있다.

1. strstr


C++ 라이브러리에서 제공하는 헤더 파일인 cstring에서는 검색 대상 문자열의 상수성 여부에 따라 2개의 함수가 오버로드overload되어 있다.

const char * strstr(const char * str1, const char * str2);
char * strstr(char * str1, const char * str2);

C 라이브러리에서 제공하는 헤더 파일인 string.h에서는 하나의 함수만이 정의되어 있다.

char * strstr(const char * str1, const char * str2);
str1
검색 대상이 될 문자열이다.
str2
찾을 문구이다.

함수의 실행 결과 str2에서 지정한 문자열이 발견된 위치를 포인터로 반환한다. 그런 문구가 없으면 NULL이 반환된다.

다음은 strstr 함수의 실행 예이다.

/* strstr.c */
#include <stdio.h>
#include <string.h>

int main(int argc, char * argv[])
{
	char str1[64] = "What the hell! Why don't you clean this room?";
	char str2[64] = "What the f*ck! Why don't you clean this room?";
	char str3[64] = "f*ck";
	char * result = NULL;

	result = strstr(str1, str3);
	if (result != NULL)
	{
		// contains forbidden word
		printf("strstr(\"%s\", \"%s\")\n = \"%s\";\n", str1, str3, result);
		printf("forbidden word: %s\n", result);
	}
	else
	{
		// not contains forbidden word
		printf("strstr(\"%s\", \"%s\")\n = NULL;\n", str1, str3);
		printf("forbidden word: none\n");
	}

	printf("\n");

	result = strstr(str2, str3);
	if (result != NULL)
	{
		// contains forbidden word
		printf("strstr(\"%s\", \"%s\")\n = \"%s\";\n", str2, str3, result);
		printf("forbidden word: %s\n", result);
	}
	else
	{
		// not contains forbidden word
		printf("strstr(\"%s\", \"%s\")\n = NULL;\n", str2, str3);
		printf("forbidden word: none\n");
	}

	return 0;
}
[그림 1] strstr.c 예제 소스 코드
[그림 2] strstr.c 예제 소스 코드의 실행 결과

위 코드를 참조하면, 검색 대상이 될 두 개의 문자열이 준비되어 있다. 하나는 "What the hell! Why don't you clean this room?"로서 욕설이 없는 문자열을 포함하는 str1이고, 다른 하나는 "What the f*ck! Why don't you clean this room?"로서 욕설이 있는 문자열을 포함하는 str2이다. 그리고 "f*ck"로서 검색할 욕 단어를 포함하는 str3가 준비되어 있다.

result = strstr(str1, str3);로서 str1에 대해 욕 단어가 있는지를 검색한다. 이 문자열에는 욕 단어가 포함되어 있지는 않으므로 NULL가 반환되며 result 포인터 변수에도 NULL이 보관된다.

result = strstr(str2, str3);로서 str2에 대해 욕 단어가 있는지를 검색한다. 이 문자열에는 str3에서 지정한 욕 단어가 포함되어 있으므로 해당 욕 단어가 시작되는 포인터가 반환되고 result 변수에 이 주소가 보관된다. 그러므로 printf로 이 변수를 출력하면 해당 욕으로 시작되는 부분 문자열이 출력되는 것이다.

1-1. Wide Character 확장 함수 - wcsstr


상기 strstr은 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 복사는 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

C++ 헤더인 cwchar에는 상수성 여부에 따라 다음의 두 함수가 오버로드되어 있다.

const wchar_t * wcsstr(const wchar_t * str1, const wchar_t * str2);
wchar_t * wcsstr(wchar_t * str1, const wchar_t * str2);

C 헤더인 wchar.h에는 하나의 함수만이 정의되어 있다.

wchar_t * wcsstr(const wchar_t * str1, const wchar_t * str2);

<Epilogue>


본 포스팅을 통해 문자열의 문자열 검색 함수에 대해 정리해 보았다. 다음 포스팅[libc 문자열 조작 함수 정리 (part 06 - strtok)]에서는 특정 기호로 구분된 문자열을 분할해주는 strtok 함수에 대해 정리한다.

카테고리 “Language/C & C++”
more...
썸네일 이미지
libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)
libc 문자열 조작 함수 정리 C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로..
Language/C & C++
2018. 8. 20. 11:38

libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)

Language/C & C++
2018. 8. 20. 11:38

libc 문자열 조작 함수 정리


C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로 하여 작성되었다.

  1. libc 문자열 조작 함수 정리 (part 01 - strcpy, strncpy)
  2. libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)
  3. libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)
  4. libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)
  5. libc 문자열 조작 함수 정리 (part 05 - strstr)
  6. libc 문자열 조작 함수 정리 (part 06 - strtok)
  7. libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)
  8. libc 문자열 조작 함수 정리 (part 08 - strlen)
  9. libc 문자열 조작 함수 정리 (part 09 - strpbrk)
  10. libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)
  11. libc 문자열 조작 함수 정리 (part 11 - strerror)

Part IV. strchr, strrchr


본 포스팅에서는 문자열로부터 특정 문자character의 검색을 지원하는 함수인 strchr 계열의 함수에 대해 사용 예를 정리한다.

<Prologue>


strchrstrrchr는 문자열 중에서 특정 문자를 검색하여 그 위치를 얻는데 사용된다. 좀 더 엄밀히 말하면 그 문자가 시작되는 위치의 포인터를 반환한다. strchr은 문자열을 순방향으로 검색하여 가장 처음으로 발견될 때의 위치를 반환하고, strrchr은 문자열을 역방향으로 검색하여 가장 처음으로 발견될 때의 위치를 반환한다.

예를 들면 "What can I do for that?"라는 문자열에서 소문자 'a'로 시작되는 문자열을 얻고자 할 때,

strchr은 문자열을 첫 글자부터 순서대로 검색하여 ▶"What can I do for that?"와 같이 3번째에 있는 'a'를 검색하여 이 문자로 시작하는 문자열을 반환하고 strrchr은 문자열을 끝 글자부터 거꾸로 검색하여 "What can I do for that?"◀와 같이 맨 나중에 있는 'a'를 검색하여 이 문자로 시작하는 문자열을 반환한다.

1. strchr


C++ 라이브러리에서 제공하는 헤더 파일인 cstring에서는 검색 대상 문자열의 상수성 여부에 따라 2개의 함수가 오버로드overload되어 있다.

const char * strchr(const char * str, int character);
char * strchr(char * str, int character);

C 라이브러리에서 제공하는 헤더 파일인 string.h에서는 하나의 함수만이 정의되어 있다.

char * strchr(const char * str, int character);
str
검색 대상이 되는 문자열이다.
character
검색하고자 하는 특정 문자이다.

함수의 실행 결과 character의 문자로 시작하는 부분 문자열을 반환한다. 찾을 수 없으면 NULL을 반환한다.

원형을 자세히 보면 문자 매개변수 characterchar 형이 아닌 int 형으로 선언되어 있다. 참고로 char 형은 1 바이트, int 형은 CPU가 처리할 수 있는 기본 단위로 정의된다. 예를 들어 16 비트 CPU에서는 int가 2 바이트이고, 32 비트 CPU에서는 int가 4 바이트이며, 64 비트 CPU에서는 int가 8 바이트이다. C 언어에서 int보다 작은 크기의 데이터가 전달된다고 하더라도 CPU는 내부의 전자 회로 설계상 int 형으로 처리한다.

다음은 strchr의 사용 예이다.

/* strchr.c */
#include <stdio.h>
#include <string.h>

int main(int argc, char * argv[])
{
	char str[64] = "What can I do for that?";             // original string
	char * result = NULL;                                // result of strchr

	result = strchr(str, 'a');                     // find the character 'a'
	if (result != NULL)
	{
		printf("strchr(\"%s\", 'a') = \"%s\";\n", str, result);
		printf("strchr(\"%s\", 'a') : %ld(th) character.\n", str, (result - str) + 1);
	}

	return 0;
}
[그림 1] strchr.c 예제 소스 코드
[그림 2] strchr.c 예제 소스 코드의 실행 결과

위 코드와 실행 결과를 참고하면, strchr(str, 'a');을 실행함으로써 대상 문자열의 3번째 위치한 'a'를 검색하였고 이 문자부터 시작하는 부분 문자열 "at can I do for that?"을 반환하였다. 반환된 문자열은 원본 문자열의 일부이므로 포인터간 뺄셈 연산을 사용하여 몇 번째 문자로서 검색되었는지를 확인할 수 있다. ((result - str) + 1)

1-1. Wide Character 확장 함수 - wcschr


상기 wcschr는 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 검색은 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

C++ 헤더인 cwchar에는 상수성 여부에 따라 다음의 두 함수가 오버로드되어 있다.

const wchar_t * wcschr(const wchar_t * str, wchar_t character);
wchar_t * wcschr(wchar_t * str, wchar_t character);

C 헤더인 wchar.h에는 하나의 함수만이 정의되어 있다.

wchar_t * wcschr(const wchar_t * ws, wchar_t wc);

2. strrchr


strrchr 함수도 위의 strchr 함수와 동일하다. 다만, 검색 방향이 문자열의 맨 끝 문자부터 검색한다는 차이만이 있다. 함수의 원형은 다음과 같다.

C++ 헤더인 cwchar에는 상수성 여부에 따라,

const char * strrchr(const char * str, int character);
char * strrchr(char * str, int character);

C 헤더인 wchar.h에는,

char * strrchr(const char * str, int character);
str
검색 대상이 되는 문자열이다.
character
검색하고자 하는 특정 문자이다.

다음은 strrchr의 사용 예이다.

/* strrchr.c */
#include <stdio.h>
#include <string.h>

int main(int argc, char * argv[])
{
	char str[64] = "What can I do for that?";             // original string
	char * result = NULL;

	result = strrchr(str, 'a');                    // find the character 'a'
	if (result != NULL)
	{
		printf("strrchr(\"%s\", 'a') = \"%s\";\n", str, result);
		printf("strrchr(\"%s\", 'a') : %ld(th) character.\n", str, (result - str) + 1);
	}

	return 0;
}
[그림 3] strrchr.c 예제 소스 코드
[그림 4] strrchr.c 예제 소스 코드의 실행 결과

[그림 2]와 [그림 4]의 실행결과를 비교해보면, result = strchr(str, 'a');을 실행 시 순방향으로 검색이 이루어지므로 최초 발견되는 문자는 3번째 문자인 'a'가 되어 "at can I do for that?" 문자열이 반환되지만 result = strrchr(str, 'a');을 실행 시 역방향으로 검색이 이루어지므로 최초 발견되는 문자는 21번째 문자인 'a'가 되어 "at?" 문자열이 반환됨을 알 수 있다.

2-1. Wide Character 확장 함수 - wcsrchr


상기 wcsrchr는 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 검색은 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

C++ 헤더인 cwchar에는 상수성 여부에 따라 다음의 두 함수가 오버로드되어 있다.

const wchar_t * wcsrchr(const wchar_t * str, wchar_t character);
wchar_t * wcsrchr(wchar_t * str, wchar_t character);

C 헤더인 wchar.h에는 하나의 함수만이 정의되어 있다.

wchar_t * wcsrchr(const wchar_t * ws, wchar_t wc);

<Epilogue>


본 포스팅을 통해 문자열의 문자 검색 함수에 대해 정리해 보았다. 다음 포스팅[libc 문자열 조작 함수 정리 (part 05 - strstr)]에서는 문자열 내에 특정 문자열을 찾아주는 strstr 함수에 대해 정리한다.

카테고리 “Language/C & C++”
more...
썸네일 이미지
libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)
libc 문자열 조작 함수 정리 C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로..
Language/C & C++
2018. 8. 19. 20:17

libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)

Language/C & C++
2018. 8. 19. 20:17

libc 문자열 조작 함수 정리


C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로 하여 작성되었다.

  1. libc 문자열 조작 함수 정리 (part 01 - strcpy, strncpy)
  2. libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)
  3. libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)
  4. libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)
  5. libc 문자열 조작 함수 정리 (part 05 - strstr)
  6. libc 문자열 조작 함수 정리 (part 06 - strtok)
  7. libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)
  8. libc 문자열 조작 함수 정리 (part 08 - strlen)
  9. libc 문자열 조작 함수 정리 (part 09 - strpbrk)
  10. libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)
  11. libc 문자열 조작 함수 정리 (part 11 - strerror)

Part III. strcmp, strncmp


본 포스팅에서는 문자열 비교compare를 지원하는 함수인 strcmp 계열의 함수에 대해 사용 예를 정리한다.

<Prologue>


정수, 실수 등의 원시 자료형primitive data type에서는 두 변수간의 비교 연산자로 ==를 사용한다. 그러나 C 언어에서 문자열은 포인터pointer의 일종이기 때문에 == 연산자를 사용할 경우 두 포인터 간 주소 비교만이 수행되지, 각 주소에 보관된 문자까지는 비교하지 않는다. 포인터가 가리키는 주소를 참조하여 각 문자를 비교함으로써 문자열의 일치 여부를 확인하기 위해서는 strcmp 함수를 사용한다.

1. strcmp


strcmp 함수의 원형은 다음과 같다.

int strncmp(const char * str1, const char * str2);
str1
비교를 수행할 첫 번째 문자열이다.
str2
비교를 수행할 두 번째 문자열이다.

strcmp는 두 문자열을 서로 비교한 결과를 반환한다.

일치는 0, 사전 순서대로 볼 때 str1이 더 앞쪽에 있다면 음수, str2가 더 앞쪽에 있다면 양수를 반환한다.

/* strcmp.c */
#include <stdio.h>
#include <string.h>

int main(int argc, char * argv[])
{
	char str1[64] = "codingCat";                           // the 1st string
	char str2[64] = "codingTiger";                         // the 2nd string
	char str3[64] = "codingCat";                           // the 3rd string
	int result = 0;

	result = strcmp(str1, str3);            //   "codingCat" = "codingCat"
	printf("compare: \"%s\" vs \"%s\" >> %d\n", str1, str3, result);

	result = strcmp(str1, str2);            //   "codingCat" > "codingTiger"
	printf("compare: \"%s\" vs \"%s\" >> %d\n", str1, str2, result);

	result = strcmp(str2, str3);            // "codingTiger" < "codingCat"
	printf("compare: \"%s\" vs \"%s\" >> %d\n", str2, str3, result);

	return 0;
}
[그림 1] strcmp.c 예제 소스 코드
[그림 2] strcmp.c 예제 소스 코드의 실행 결과

주목할 사항은 strcmp의 반환 값이다. 앞서 설명한 바와 같이 두 문자열이 같다면 0을 반환하므로 0인지 아닌지만 확인할 수도 있겠지만, 두 문자열이 서로 다를 경우에 그 우선순위와 두 문자열간 거리 정보를 얻을 수 있다. 예를 들어, 위 코드를 참고하여 strcmp(str1, str2);를 실행할 경우 str1str2"coding"까지만 동일하고 그 다음 문자는 각각 'C''T'로서 일치하지 않는다. 이 때,

'C''T'보다 앞서므로 str1str2보다 앞선다. 즉, strcmp음수를 반환한다.

strcmp가 반환할 음수는 구체적으로 두 문자열 사이의 거리이다. 'C''T'보다 17번째 앞선 문자이므로(C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T) -17을 반환한다.

strcmp의 매개변수를 바꾸었을 경우 또한 원리는 같다. 위 코드를 참고하여 strcmp(str2, str1);를 실행할 경우 str2str1"coding"까지만 동일하고 그 다음 문자는 각각 'T''C'로서 일치하지 않는다. 이 때,

'T''C'보다 앞서므로 str2str1보다 처진다. 즉, strcmp양수를 반환한다.

strcmp가 반환할 양수는 구체적으로 두 문자열 사이의 거리이다. 'T''C'보다 17번째 뒤에 나오는 문자이므로(C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T) +17을 반환한다.

요약하면,

  • strcmp = 0이면 두 문자열은 일치한다.
  • strcmp ≠ 0이면 두 문자열은 다르다.
    • strcmp > 0이면 첫 번째 문자열이 두 번째 문자열보다 늦다. 절댓값을 취함으로써 서로 일치하지 않는 최초의 문자 사이의 거리를 알 수 있다.
    • strcmp < 0이면 첫 번째 문자열이 두 번째 문자열보다 앞선다. 절댓값을 취함으로써 서로 일치하지 않는 최초의 문자 사이의 거리를 알 수 있다.

1-1. Wide Character 확장 함수 - wcscmp


상기 strcmp는 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 비교는 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

int wcscmp (const wchar_t* wcs1, const wchar_t* wcs2);

2. strncmp


strncmp은 문자열의 일부에 대해서만 비교 연산을 수행하고 나머지 원리는 위에서 정리한 바와 같다.

int strncmp(const char * str1, const char * str2, size_t num);
str1
비교를 수행할 첫 번째 문자열이다.
str2
비교를 수행할 두 번째 문자열이다.
num
비교를 수행할 최대 문자수이다.

마찬가지로 비교 결과를 반환한다. 일치는 0, 사전 순서대로 볼 때 str1이 더 앞쪽에 있다면 음수, str2가 더 앞쪽에 있다면 양수를 반환한다.

/* strncmp.c */
#include <stdio.h>
#include <string.h>

int main(int argc, char * argv[])
{
	char str1[] = "codingCat";                             // the 1st string
	char str2[] = "codingTiger";                           // the 2nd string
	int result = 0;

	result = strncmp(str1, str2, 11);       //   "codingCat" ! "codingTiger"
	printf("strncmp(\"%s\", \"%s\", 11) == %d\n", str1, str2, result);

	result = strncmp(str2, str1, 11);       // "codingTiger" ! "codingCat"
	printf("strncmp(\"%s\", \"%s\", 11) == %d\n", str2, str1, result);

	result = strncmp(str1, str2, 6);        //      "coding" = "coding"
	printf("strncmp(\"%s\", \"%s\", 6) == %d\n", str1, str2, result);

	result = strncmp(str2, str1, 6);        //      "coding" = "coding"
	printf("strncmp(\"%s\", \"%s\", 6) == %d\n", str2, str1, result);

	return 0;
}
[그림 3] strncmp.c 예제 소스 코드
[그림 4] strcmp.c 예제 소스 코드의 실행 결과

2-1. Wide Character 확장 함수 - wcsncmp


상기 strncmp는 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 비교는 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

int wcsncmp (const wchar_t * wcs1, const wchar_t * wcs2, size_t num);

<Epilogue>


본 포스팅을 통해 문자열 비교 함수에 대해 정리해 보았다. 다음 포스팅[libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)]에서는 문자열에서 특정 문자를 검색하는 strchr, strrchr 함수에 대해 정리한다.

카테고리 “Language/C & C++”
more...
썸네일 이미지
libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)
libc 문자열 조작 함수 정리 C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로..
Language/C & C++
2018. 8. 18. 23:38

libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)

Language/C & C++
2018. 8. 18. 23:38

libc 문자열 조작 함수 정리


C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로 하여 작성되었다.

  1. libc 문자열 조작 함수 정리 (part 01 - strcpy, strncpy)
  2. libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)
  3. libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)
  4. libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)
  5. libc 문자열 조작 함수 정리 (part 05 - strstr)
  6. libc 문자열 조작 함수 정리 (part 06 - strtok)
  7. libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)
  8. libc 문자열 조작 함수 정리 (part 08 - strlen)
  9. libc 문자열 조작 함수 정리 (part 09 - strpbrk)
  10. libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)
  11. libc 문자열 조작 함수 정리 (part 11 - strerror)

Part II. strcat, strncat


본 포스팅에서는 문자열 결합concatenate을 지원하는 함수인 strcat 계열의 함수에 대해 사용 예를 정리한다.

<Prologue>


strcatstrncat는 문자열 끝에 다른 문자열을 붙이는 역할을 한다. 즉, 원래의 문자열 끝에 있던 NULL ('\0') 문자를 떼고 다른 문자열을 이어 붙인 다음 그 끝에 NULL을 새로 붙이는 역할을 한다. 이 때 원래의 문자열은 수정 가능하여야 하며(즉, 문자열 상수 안 됨), 새로 붙게 될 문자열을 수용할만큼 빈 공간이 충분해야 한다(충분하지 않은 공간에 긴 문자열을 이어붙이면, 메모리가 원래의 영역을 벗어나 다른 프로그램까지 고장나게 만든다. 이를 버퍼 오버플로우buffer overflow라고 하며, C 언어에는 타 메모리 영역의 침범을 감지하여 프로그램을 중지시키는 기능이 없기 때문에 주의해야 한다.

1. strcat


strcat의 원형은 다음과 같다.

char * strcat(char * destination, const char * source);
destination
기존의 문자열이 보관된 수정 가능한 버퍼 주소이다.
source
이어붙일 문자열을 가리키는 주소이다.

source가 가리키는 주소에서 문자를 하나씩 읽어 destination이 가리키는 주소의 문자열에 하나씩 이어 붙인다. 함수가 종료될 때 destination이 가리키는 주소를 그대로 반환한다.

다음은 strcat의 사용 예제이다.

/* strcat.c */
#include <stdio.h>
#include <string.h>

int main(int argc, char * argv[])
{
	char str1[64] = "Cat";                               // string to append
	char str2[64] = "coding";                               // string buffer

	printf("<BEFORE>\n");
	printf("str1 = \"%s\"\n", str1);
	printf("str2 = \"%s\"\n", str2);

	strcat(str2, str1);                                       // concatenate

	printf("<AFTER>\n");
	printf("str1 = \"%s\"\n", str1);
	printf("str2 = \"%s\"\n", str2);

	return 0;
}
[그림 1] strcat.c 예제 소스 코드
[그림 2] strcat.c 예제 소스 코드의 실행 결과

1-1. Wide Character 확장 함수 - wcscat


상기 wcsncat는 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 결합은 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

wchar_t * wcscat (wchar_t * destination, const wchar_t * source);

2. strncat


strncat도 문자열을 이어붙이는 역할을 하는데 매개변수로 지정한 몇 글자까지만 이어붙인다.

char * strncat(char * destination, const char * source, size_t num);
destination
원래의 문자열을 가리키는 수정 가능한 메모리 주소이다.
source
이어붙일 문자열을 가리키는 주소이다.
num
source로부터 복사할 문자의 개수이다.

source가 가리키는 주소에서 문자를 하나씩 읽어 destination이 가리키는 주소의 문자열에 이어붙이는데 그 개수는 num 개로 제한한다. 함수가 종료될 때 destination이 가리키는 주소를 그대로 반환합니다.

아래 코드는 strncat의 사용 예이다.

/* strncat.c */
#include <stdio.h>
#include <string.h>

int main(int argc, char * argv[])
{
	char str1[64] = "Cat, Tiger and Lion";               // string to append
	char str2[64] = "coding";                               // string buffer

	printf("<BEFORE>\n");
	printf("str1 = \"%s\"\n", str1);
	printf("str2 = \"%s\"\n", str2);

	strncat(str2, str1, 3);                                   // concatenate

	printf("<AFTER>\n");
	printf("str1 = \"%s\"\n", str1);
	printf("str2 = \"%s\"\n", str2);

	return 0;
}
[그림 3] strncat.c 예제 소스 코드
[그림 4] strncat.c 예제 소스 코드의 실행 결과

2-1. Wide Character 확장 함수 - wcsncat


상기 wcsncat는 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 결합은 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

wchar_t * wcsncat (wchar_t * destination, const wchar_t * source, size_t num);

<Epilogue>


본 포스팅을 통해 문자열 복사 함수에 대해 정리해 보았다. 다음 포스팅[libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)]에서는 문자열을 비교하는 strcmp, strncmp 함수에 대해 정리한다.

카테고리 “Language/C & C++”
more...
썸네일 이미지
libc 문자열 조작 함수 정리 (part 01 - strcpy, strncpy)
libc 문자열 조작 함수 정리 C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로..
Language/C & C++
2018. 8. 18. 06:34

libc 문자열 조작 함수 정리 (part 01 - strcpy, strncpy)

Language/C & C++
2018. 8. 18. 06:34

libc 문자열 조작 함수 정리


C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로 하여 작성되었다.

  1. libc 문자열 조작 함수 정리 (part 01 - strcpy, strncpy)
  2. libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)
  3. libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)
  4. libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)
  5. libc 문자열 조작 함수 정리 (part 05 - strstr)
  6. libc 문자열 조작 함수 정리 (part 06 - strtok)
  7. libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)
  8. libc 문자열 조작 함수 정리 (part 08 - strlen)
  9. libc 문자열 조작 함수 정리 (part 09 - strpbrk)
  10. libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)
  11. libc 문자열 조작 함수 정리 (part 11 - strerror)

Part I. strcpy, strncpy


본 포스팅에서는 문자열 복사copy를 지원하는 함수인 strcpy 계열의 함수에 대해 사용 예를 정리한다.

<Prologue>


strcpy와 strncpy는 둘 다 문자열을 복사하는 함수이다. 복사의 목적은 여러가지가 있겠으나 대체로 원본은 그대로 두어 보존하고, 사본을 하나 만들어서 변조도 하고 수정도 하고 그러기 위함이 대부분이다. C 언어에서 문자열은 끝에 항상 NULL('\0') 문자가 붙는다. 화면으로 보이지는 않지만 분명 메모리상으로는 존재하는 것이 이 문자이다. 문자열의 복사는 이 NULL 문자를 만나면 중단하게 되어 있다.

악의적인 목적으로 문자열에서 NULL 문자를 제거해버리면 컴퓨터는 NULL 문자를 찾을때까지 컴퓨터에 설치된 메모리 전체를 쑤시고 다니면서 자신과 무관한 프로그램이 점유하는 메모리의 내용까지 침범하는데 프로그램을 잘못 짜서 자신의 컴퓨터에서만 일어나면 버그bug지만, 다른 컴퓨터를 그렇게 만든다면 훌륭한 사이버 공격이다. 2003년 1월 25일에 우리나라에서 발생한 1.25 인터넷 대란에서 SQL 서버를 쓰러뜨린 슬래머 웜도 이와 같은 원리이다.

1. strcpy


어쨌든, strcpy는 다음과 같은 원형을 가지며 문자열을 통째로 복사하는 역할을 한다.

char * strcpy(char * destination, const char * source);
destination
사본이 저장될 문자열 버퍼이다.
source
원본 문자열이 담긴 문자열 버퍼이다.

source가 가리키는 주소에서 문자를 하나씩 읽어 destination이 가리키는 주소에 하나씩 베껴적는데 NULL 문자를 만나면 그것까지 복사한 다음에 함수를 종료한다. 함수가 종료될 때 destination이 가리키는 주소를 그대로 반환한다. 다음은 strcpy 함수를 사용하는 예이다.

/* strcpy.c */
#include <stdio.h>
#include <string.h>

int main(int argc, char * argv[])
{
	char str1[64] = "Hello, World!";                        // source string
	char str2[64] = {'\0', };                               // string buffer

	printf("<BEFORE>\n");
	printf("str1 = \"%s\"\n", str1);
	printf("str2 = \"%s\"\n", str2);

	strcpy(str2, str1);                                     // copy string

	printf("<AFTER>\n");
	printf("str1 = \"%s\"\n", str1);
	printf("str2 = \"%s\"\n", str2);

	return 0;
}
[그림 1] strcpy.c 예제 소스 코드
[그림 2] strcpy.c 예제 소스 코드의 실행 결과

1-1. Wide Character 확장 함수 - wcscpy


상기 strcpy는 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 복사는 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

wchar_t * wcscpy (wchar_t * destination, const wchar_t * source);

2. strncpy


strncpy도 문자열 복사이긴 한데 원본 문자열의 글자부터 지정된 수만큼의 문자만을 복사한다. 복사된 문자열의 끝에는 자동으로 NULL이 붙는다. 원형은 다음과 같다.

char * strncpy(char * destination, const char * source, size_t num);
destination
사본이 저장될 메모리이다.
source
원본 문자열이 담긴 메모리이다.
num
처음부터 몇 글자까지만 복사할 것인지 지정합니다.

source가 가리키는 주소에서 문자를 하나씩 읽어 destination이 가리키는 주소에 복사하는데, NULL 문자를 만나면 그것까지만 복사한 다음에 함수를 종료한다. 또는 현재까지 복사된 문자 수가 num에서 지정한 개수와 같아도 함수를 종료한다. 함수가 종료될 때 destination이 가리키는 문자열 버퍼의 주소를 그대로 반환한다. 다음은 strncpy 함수의 사용법이다.

/* strncpy.c */
#include <stdio.h>
#include <string.h>

int main(int argc, char * argv[])
{
	char str1[64] = "Hello, World!";                        // source string
	char str2[64] = {'\0', };                               // string buffer

	printf("<BEFORE>\n");
	printf("str1 = \"%s\"\n", str1);
	printf("str2 = \"%s\"\n", str2);

	strncpy(str2, str1, 5);                                 // copy string

	printf("<AFTER>\n");
	printf("str1 = \"%s\"\n", str1);
	printf("str2 = \"%s\"\n", str2);

	return 0;
}
[그림 3] strncpy.c 예제 소스 코드
[그림 4] strncpy.c 예제 소스 코드의 실행 결과

2-1. Wide Character 확장 함수 - wcsncpy


상기 wcsncpy는 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 복사는 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

wchar_t * wcsncpy (wchar_t * destination, const wchar_t * source, size_t num);

<Epilogue>


본 포스팅을 통해 문자열 복사 함수에 대해 정리해 보았다. 다음 포스팅[libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)]에서는 문자열 끝에 다른 문자열을 이어 붙이는 strcat, strncat 함수에 대해 정리한다.

카테고리 “Language/C & C++”
more...

“2018/08” (13건)