각종 함수를 통하여 랜덤 배열에서 최대 최소 평균값 찾아내기.
'언어 > mips' 카테고리의 다른 글
mips_example1 (0) | 2017.12.26 |
---|---|
Branch_jump_jr_매크로&함수_for문_if문 (0) | 2017.12.26 |
li_srl_sll_or_nor_zero_la_lw (0) | 2017.12.26 |
기본구조_보수_데이터_immediate (0) | 2017.12.26 |
각종 함수를 통하여 랜덤 배열에서 최대 최소 평균값 찾아내기.
mips_example1 (0) | 2017.12.26 |
---|---|
Branch_jump_jr_매크로&함수_for문_if문 (0) | 2017.12.26 |
li_srl_sll_or_nor_zero_la_lw (0) | 2017.12.26 |
기본구조_보수_데이터_immediate (0) | 2017.12.26 |
나의 학번을 통하여 자유로운 진수변환
mips_example2 (0) | 2017.12.26 |
---|---|
Branch_jump_jr_매크로&함수_for문_if문 (0) | 2017.12.26 |
li_srl_sll_or_nor_zero_la_lw (0) | 2017.12.26 |
기본구조_보수_데이터_immediate (0) | 2017.12.26 |
기능:실행 위치를 순차적인 것에서 벗어나 실행하게 된다.
jump와 function call 의 차이
jump는 address>address+4>address+8----jump---->new address 이렇게 가는 것을 의미한다.
function calld은 argument를 주고받아 함수를 호출한다. 레지스터값 데이터값들을 저장 및 복원한다.
4byte 28byte
0000 ___00 | 32바이트중 4바이트는 page로 사용
jump의 구조는
opcode jump target address
6byte 26byte로 이루어져 있다.
위를 보면 ADDRESS가 0040000 0040004 0040008...이런식으로 되어있다.
순수대로 나가다가 J format에서 0040028로 jump하라는 지시가 나오게 되고 이를 수행한다.
중간은 아얘 훑지도 않고 그냥 지나가버린다.
address 는 총 32byte 인데 어떻게 26byte에 저장할까?
page는 그대로 베끼고 address에서 뒤에 00 빼고 26바이트를 대입시켜준다.
즉 page는 변경 불가능하다.!
page를 변경하고 싶다면 jr을 사용한다.
jr의 사용방식은 jr $<레지스터> 이다. 즉 레지스터의 2^32의 데이터를 모두 사용하여 주소를 저장하는 것이다.
jr $
branch는 I instruction format으로 맨뒤 2^16에 operand를 저장한다.
맨앞자리가 sign bit이므로 –2^15~2^15를 저장하게 된다.
즉 2^15bit씩 메모리를 이동할 수 있게된다. 하지만 실제로 메모리는 2^32byte로 구성되어 있다.
(=2^30word)
위를 보면 code에 뒤에 0005라고 되어 있는데 이는 5만큼 뛰라는 의미이다. 하지만 원래는 6만큼 뛰어야 하는데 왜 5만큼만뛰는것일까?? 그것은 순차적 순서에서 미리 +1이 내장되어있기 때문이다.
#branch not equal
#s1=s2이면 j문을 실행하고 다르면 L2로 간다.
bne $s1,$s2,L2
j L1
매크로는 선언을 한후 이용하게 되면 해당 소스파일로가서 선언한 함수를 그때그때 가져오는 것이 아니라 그자체로 복사 붙여넣기가 되는 것이다. 따라서 사용시 소스파일로 가는 것이 아니기 때문에 시간이 적게걸린다. 하지만 용량이커진다. 이와 반대로 함수는 사용시마다 함수를 소스파일로 가서 빌려오고 빌려오는방식이다(argument_다른 장소에 있는 데이터 를 주고받는다.), 사용시 용량은 적지만 시간이 많이걸린다.
사용방법: 초기화 – loop-loop body- loop 조건
if문의 구조
bne $<>,$<>,else
add $...
j exit
else~
exit ~
이런 방식으로 겹치는 구간이 생기지 않도록 잘 구성시켜 준다.
**
만약
if(i<=j) 가 있다면 어ᄄᅠᇂ게 할것인가
x= x+1;
z= 1;
else
y=y-1
z=2*z
를 코딩 . $s1=i, $s2=j이다.
#같거나 작다가 없어 작다만 표현 가능하다. 따라서 i>j임과 else인 i<=j 로 나타내는 것이 편할거같아 slt를 다음과같이 작성하였다.
#t0에 1이 담기면 s2<s1. 즉 위에서 else 조건을 만족하고 t0에 0이 담기면 i<=j를 만족하여 그냥 조건을 실행하게된다.
slt $t0, $s2, $s1
#t0가 1이면 else로 간다. 즉 else에는 else 조건을 써주고 t0가 0이면 가던길 가므로 가던길에는 위에 그냥조건을 써준다.
bne $t0, $zero, else
addi $t1,$t1,1
addi $t3,$zero,1
j end
else: addi $t2,$t2,-1
addi $t3,$t3,$t3
mips_example2 (0) | 2017.12.26 |
---|---|
mips_example1 (0) | 2017.12.26 |
li_srl_sll_or_nor_zero_la_lw (0) | 2017.12.26 |
기본구조_보수_데이터_immediate (0) | 2017.12.26 |
li - load immediate. 변수를 넣을 때 사용한다. 두 번의 연산을 진행한다.
..text
li $s0,1000
li $s1,-1000
li $s2,8
#shift right logical을 통하여 값이 절반줄음
srl $t0,$s0,1
#음수에 srl. logical로 미룰시 값이 완전 상이한값이 나온다.
srl $t1,$s1,1
#srav는 shift register arithmatice variable 로 변수만큼 이동시킨다.
srav $t2,$s0,$s2
or을 통하여 겹치는 숫자 출력 가능하다,(2진수이다.동일하게 and 가능하다. )
.text
li $s1,100
li $s2,1
or $s3,$s1,$s2(101저장)
nor 과 $zero를 통하여 보수를 만들 수 있다.
int형 배열 및 숫자 메모리에 저장하는 방법
.data
<메모리명> : .word word[0],word[1],word[2]
ex)
.data
memA :.word 1,2,3,4,5
memB :.word "hihi“
la:load address-->메모리의 주소를 레지스터에 load한다.
la <레지스트리명>,<메모리명>
ex)
la $s0,memA
#그냥 memA가 가리키는 address 에 +1해주기
la $s1,memA+1
lw:load word-->메모리에 저장된 값을 레지스트리에 load 시켜주기
ex)
lw $s2,memA
#메모리의 단위는 4바이트이므로 4씩 띄워줘야 배열을 한칸씩 점프할 수 있다.
lw $s2,memA+4
lh lb
lh : load half word 로 해당 메모리에 있는 숫자 8개중 가운데를 쪼개서뒷부분을 쓴다.
사용방법 -> lh <레지스트리명>,(<레지스트리명>)-->(<레지스트리명>)을 좌측과 같이 () 안에 들어간 레지스트리명안에는 메모리의 주소가 담겨있어야 한다. 그리고 그메모리주소 안에 있는값을 가져오는 것이다.
즉 레지스트리명->메모리->메모리안 숫자 이다. 따라서 lh는 반드시 la와 동반되어야한다.
하기쉬운 오류
.text
li $s1,0x12345678
lh $s2,($s1)
이렇게하면 오류가 걸린다. 이유는 ()안에 는 메모리 주소를 사용하는것인데, $s1을 해버리면 0x12345678라는 메모리로 찾아가게 되기 때문이다.
lb-->load byte.바이트 단위로 저장시켜주겠다는 의미. 즉 4바이트 2개씩 저장한다는 의미이다.
사용 방법 ->lb <레지스트리명>,(메모리 주소가 저장된 레지스트리명)
*이역시 la와 함께 사용되어야한다.
ex)
.data
mem : .word 0x12345678
.text
la $s1,mem
#78저장
lb $s2,($s1)
#34저장
lb $s3,2($s1)
mips_example2 (0) | 2017.12.26 |
---|---|
mips_example1 (0) | 2017.12.26 |
Branch_jump_jr_매크로&함수_for문_if문 (0) | 2017.12.26 |
기본구조_보수_데이터_immediate (0) | 2017.12.26 |
간단한 밉스 사용법
-컴파일시 code는 address ,code ,basic, source 등으로 나뉘게 된다.
-address는 메모리상 데이터를 의미한다. 4단위의 byte로 쪼개진다. 이는 주소가 word 단위이기 때문이다.
-code는 실제적으로 프로세서 이해하는 기계어이다. basic은 어셈블리어이다.
우리가 basic. 즉 어셈블리어를 작성할 때 보통 16진수 4비트를 입력한다.
하지만 레지스터가 인식하는 비트는 2의 32승. 이는 16진수인 2의4승으로 8자리수.
따라서 실질적으로 어셈블러가 인식하는 basic은 16진수 8자리가 된다.
-.data, .text를 통하여 내가지금부터 적을 값이 어떤 타입의 데이터가 되는지 알려준다.
-$v0 는 systemcall 로 약속이다.
Register type 의 arithmetic opreation (연산)
register type’s instruction format
-레지스터 지정은 레지스터가 32개임에 따라 5bit만 필요하다.
-shift amunt는 이동할때만 이용하고 덧셈뺄셈등을 할 때는 unused이다.
Add subtract multiply
2source and 1 destination
-mips
add $s0(destination), $s1(source), $s2(source)
sub $t0, $s1, $s2
mul $s4, $s1, $s2
--1개의 opcode + 3개의 operand로 이루어져있다.
ex)
.text
-register operation에서 사용되는 모든 연산자는 전부 메모리가 아닌 레지스터에 있어야한다.
(몇몇 프로세스중 메모리를 허용하는것도 있기는 하다.)
f=(g+h)-(i+j) 구성해보기
-mips
#f=(g+h)-(i+j)
#변수로 저장되는 값은 s 에 고정형으로 넣어주고
#변수로 저장하지 않아도 되는 값은 t에 넣어준다.
add $t0,$s1,$s2
add $t1,$s3,$s4
sub $s5,$t0,$s1
Immediate type arithmetic opreation
기본 구조 첫 번째 데이터 : source.. resister에 저장되어 있다.
두 번째 데이터 : immeidate value.. 는 명령이 그것 자신이다.
-mips
.text
#컴파일시 s0 에 -61이 들어간다.
addi $s0,$t0,-61
#컴파일시 s0에 0xffff가 들어간다.
addi $s0,$t0,0xffff
#컴파일시 s0에 0xffff 가 들어간다.
addiu $s0,$t0,0xffff
양수와 음수
보수관계 나타내기
1111001
이 있으면
signbit가 1이다.
그러면 쭉가다가 처음으로 sign bit가 1이 아닌부분이 나오게 되면
그앞에 1부터 써준다.
그앞의 1은 그냥 읽고 뒷부분은 빼준다.
1001에서 8-1=7//-7
11111010은
8-2=6//-6
11111111
1-0=1//-1
memory operation
load 와 store 가 같이 이루어져야한다.
load :lw
store:sw
.data
mytext: .asciiz "Hi! Students\n"
yourtext: .asciiz "Hi! professor. My name is Kim Soongsil."
.text
li $v0, 4 # $v0 : service #, 4 --> print string
la $a0, mytext # $a0 : argument
syscall
li $v0, 4 # $v0 : service #, 4 --> print string
la $a0, yourtext # $a0 : argument
syscall
addi 는 add immetidate를 의미한다.
li는 pseudo instruction으로 야매 명령어를 의미한다.
li $s0, 0 # s0 = 0
li $s1, 1 # s1 = 1.이때 실제 명령어는 addiu $17,$0,0x00000001
li $s2, 2 # s2 = 2
li $s3, 0x12345678 # s3 = 0x12345678 --> pseudo-inst.
#실제 코드는 lui $1,0x00005678를 통해어 $1. 즉 $at에 upper. 위에서부터 담아라.
즉 12340000로 담고나서 oring. or. 즉 0이아닌수가 우선으로 $19에 at에 있는 데이터와 뒷자리 네자리를 oring하면 원하는 숫자를 $19레지스트리 안에 담을 수 있게된다.
ori $19,$1,0x00005678
그리고 그냥
addiu $t1,$s1,0xffff 또는
addi $t1,$s1,0xffff 이것을 쓰게되면 둘다 결과는 0000ffff인다.
과정: 어차피 레지스터는 32비트를 받아야 하므로 00000000과 0000ffff를 oring 하여 명령을 수행한다.
#
실제 immediate 코드는 2의 16승 까지 사용가능 .이는 16진수 4자리밖에 이용하지 못한다
그래서 우리가 그냥 li를 사용하더라도 실제로는 0x1234가 upper
add $s0, $s1, $s2 # s0 = 1 + 2
add $s0, $s0, $s3 # s0 = 1 + 2 + 0x12345678
sub $s0, $s0, $s3 # s0 = s0 - s3
addi $t0, $s1, 3 # t0 = s1 + 3
addi $t1, $s2, -4 # t1 = s2 - 4
addi $t2, $s1, 0xffff # signed operation :: but means 0x0000ffff
addi $t3, $s1, 0xffffffff # signed operation :: means -1
addiu $t4, $s1, 0xffff # unsigned operation
addi $t2, $s3, 0x12345 # t2 = s3 - 0x12345 (larger than 16 bit) --> pseudo-inst.
addi $t3, $s3, 0x2345 # t3 = s3 - 0x2345 ( less than 16 bit ) --> basic inst.
밉스 추가 공부
.date —데이터를 입력하겠다.여기에서 데이터는 메모리에 저장도니다.
#mytext라는 뱐수에 아스키즈값 hi를 넣는다.
mytext: .asciiz “hi”
#li는 load instruction이다. 즉 vo레지스트리에 4를더하여 print string을 불러온다.
li $v0,4
#la 로 load address를 한다.
#a0에 mytext라는 주소값을 넣어준다.
la $a0,mytext
#호출한다.
syscall
lw-load word
mips_example2 (0) | 2017.12.26 |
---|---|
mips_example1 (0) | 2017.12.26 |
Branch_jump_jr_매크로&함수_for문_if문 (0) | 2017.12.26 |
li_srl_sll_or_nor_zero_la_lw (0) | 2017.12.26 |
call by reference vs call by value vs call by pointer_g++로 헤더파일_cpp파일 동시 컴파일 (0) | 2018.02.03 |
---|---|
c++_객체지향이란?_cin_cout_cascading_우분투로 c++컴파일_flag_cout 출력소수점 정하기 (0) | 2018.02.03 |
char string (0) | 2017.12.25 |
static,vector(벡터) (0) | 2017.12.25 |
클래스 생성자 (0) | 2017.12.25 |
char string은 마지막이 null 값이 들어간다.
즉 n개의 글자가 char string 형태로 들어가면 n+1의 저장공간이 할당된다는 의미이다.
선언방법
char 배열을 선언과 동시에 초기화하면 c-string 이 되고 선언과 초기화를 따로하게되면 c-array 이다.
-char short[] = “abc”; 와같은 경우 자동으로 4개의 공간이 할당되는 c-string이지만 추천하지는 않는다.
-char short[] = {‘a’,b‘’,‘c’} 같은 것은 array 이다.
-char short[5]=“abcd”로 한후에 short[4]=a 이런식으로 하면 공백에 값이 들어가게 되므로 c-string--->c-array가된다.
-cstring의 각종함수
cstring으로 선안된 배열에 문자열을 넣기위해서 =처럼 대입연산자는 사용 불가능하다.
strcpy(복사할곳,복사할것); <--이런형태로 대입한다.
strcat((붙일곳,붙혀질것));
strcmp((비교1,비교2)); :동일하면 0을 리턴하고 동일하지않다면 다른값을 리턴한다.
strlength(); : 길이를 알려준다.
cout << a<<b처럼 operator overloading이 되어져 있다.
cin >> a>> b 처럼 cin으로 할때에는 cin하는 문장에서 spacebar단위로 a, b에 string이 입력된다.
만약 한줄 단위로 입력해주고 싶다면 getline을 해준다.
cin.getline(a,80) -->a라는 cstring에 80글자 내로 한줄을 입력받는다.
만약 c-string 이 아닌 그냥 string을 get line 해주고 싶다면
getline(cin,변수명) -->이렇게 해주면 된다.
굉장히 헷갈릴 것이다.
c++_객체지향이란?_cin_cout_cascading_우분투로 c++컴파일_flag_cout 출력소수점 정하기 (0) | 2018.02.03 |
---|---|
github_cpp (0) | 2017.12.26 |
static,vector(벡터) (0) | 2017.12.25 |
클래스 생성자 (0) | 2017.12.25 |
sort,structure(구조체) (0) | 2017.12.25 |
static
폰을 끄더라도 dram,sram으로 인하여.. 즉 static메모리가 존재하여 데이터를 유지한다.
즉 static메모리는 파워가 없어도 데이터를 유지한다.
static variable – 자기가 정의된 함수밖에서도 메모리가 유지된다. 일반적인 변수나 함수는 함수 안에서만 유지가 된다.
static function
프로그램이 loading할 때 global 공간에서 생성이되고 실행중에는 만들어지지 않는다
즉 static int nA=3; 을하면 global공간에 만들어지고 앞이 sheild가 쳐져서 보이지 않게된다.
#include "stdafx.h"
#include
using namespace std;
void ShowStaticVariableExample();
int main()
{
ShowStaticVariableExample();
ShowStaticVariableExample();
return 0;
}
//nA는 사용할때마다 죽어서 초기화되면 살아있던게 죽고 다시생김
void ShowStaticVariableExample()
{
static int nA = 3;
nA = nA + 1;
cout << nA << endl;
}
static member variable 은 program이 loading 할 때 공간이 잡힌다. 즉 객체 생성시에는 더 이상 공간이 잡히지 않는다.
매개변수로 포인터를 받고, 이를 배열에 넣기 위해서는 바로는 방법이없다.
이유는 포인터=배열이름을 통하여 포인터의 정의는 가능하지만
배열이름=포인터를 통하여 배열이름으로 다른 포인터로 재정의는 불가능하다.
따라서 set함수를 통하여 배열[]=포인터[] 꼴로 해준다.
CTheater::CTheater(string strName, int nTriptime, int* pnShowtime)
{
m_strName = strName;
m_nTripTime = nTriptime;
SetShowTime(pnShowtime, 10);
} static을 이러한 방식으로 사용한다면 오류가 뜬다.
즉 CTheater가 static변수를 사용하기 위해서는 .cpp나 함수위에
int CTheater::m_nTheater = 0;
이것을 위에 써준다. CTheater 이라는 클래스의 static변수를 0으로 초기화 한다는 의미이다.
그냥 static 변수를 함수에서 사용하기 위해서는 함수 위에 static 메모리를 미리 선언 및 초기화를 해준다고 생각하자. 함수를 사용할 때에는 헤더파일에서 맨앞에 static을 붙여주고 이를 정의하는 부분은 그냥 쓴다. 그리고 사용할 때 클래스명::함수명() 이렇게 사용하여 함수가 클래스변수에 포함되는 것이 아닌 그냥 사용 가능하도록 한다.
그것이 static의 방식이다. 어차피 static은 포함되는 것이 아니라 그냥 사용한다 하더라도 그값이 유지가 되어 버리기 때문에 동일하게 된다.
array는 한번정한 크기를 바꿀 수 없다.
vector는 크기를 가변적으로 바꿀 수 있다.
array는
int pA[10]; int *pnA; pnA=new int[10];
vector는 크기가 가변적인다.
array가 커지기도 하고 작아지기도 한다.
vecter
데이터를 넣을 때에는 v.push_back이라는 함수를 넣는다. 최초로 넣을때에는 push back을 한다.
벡터는 stl-templet 형태의 library 이다.
하나의 틀을 만들어 두고 그틀로 클래스를 만든다.
각각의 type를 넣으면 컴파일러가 그 type을 넣어 코딩을 해준다.
int vertor, double vector을 만드는데 templet을 만들면 int를 쓰면 컴파일러가 자동 generation 해준다.
github_cpp (0) | 2017.12.26 |
---|---|
char string (0) | 2017.12.25 |
클래스 생성자 (0) | 2017.12.25 |
sort,structure(구조체) (0) | 2017.12.25 |
array(배열) (0) | 2017.12.25 |
생성과 용례
char string (0) | 2017.12.25 |
---|---|
static,vector(벡터) (0) | 2017.12.25 |
sort,structure(구조체) (0) | 2017.12.25 |
array(배열) (0) | 2017.12.25 |
영화 별점 표본 검사 코드 (0) | 2017.12.25 |
1)selection sort 뽑을 때 작은수나 큰수부터 차례대로 뽑음
2)insertion sort 넣을 때 작은수나 큰수부터 차례대로 넣음
ex)원하는 숫자를 넣으면 알아서 순서를 배열해주는 알고리즘
{.cpp}
// ConsoleApplication27.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//
#include "stdafx.h"
#include <iostream>
using namespace std;
const int declared_size = 20;
void fillarray(int a[], int size, int& numberused);
int indexofsmallest(int a[], int startindex, int numberused);
void sort(int a[], int numberused);
void swapvalues(int& v1, int& v2);
int search(int a[], int numberused, int target);
int main()
{
cout << "this program sort\n";
int samplearray[10], numberused;
fillarray(samplearray, 10, numberused);
sort(samplearray, numberused);
cout << "it is \n";
for (int index = 0; index < numberused; index++)
{
cout << samplearray[index] << "";
cout << endl;
}
return 0;
}
void fillarray(int a[], int size, int& numberused)
{
cout << "enter" << size << " no negative nube\n mark the endof lis is negative ";
int next, index = 0;
cin >> next;
while ((next > 0) && (index < size))
{
a[index] = next;
index++;
cin >> next;
}
numberused = index;
cout << "index is"<<index << endl;
}
void sort(int a[], int numberused)
{
int indexofnextsmallest;
//맨마지막에 두개가 남을때 이것을 한번만 돌리면된다. 이유는 둘중작은거 하나만 찾으면 되기 때문이다.
for (int index = 0; index < numberused - 1; index++)
{
indexofnextsmallest = indexofsmallest(a, index, numberused);
swapvalues(a[index], a[indexofnextsmallest]);
}
}
void swapvalues(int&v1, int& v2)
{
int temp;
temp = v1;
v1 = v2;
v2 = temp;
}
int indexofsmallest(int a[], int startindex,int numberused)
{
//min값에 a의 시작값을 넣어준다.
int min = a[startindex];
//indexofmin에는 index의 시작값을 넣어준다.
int indexofmin = startindex;
//a의 index는 startindex+1부터 시작된다.
//만약 a[index]가 a의 맨시작점인 min값보다 작다면 min값에 작은값a을 넣어준다.
//indexofmin에는 작은값을 갖는 index값을 넣어준다.
for (int index=startindex+1;index < numberused; index++)
if (a[index] < min)
{
min = a[index];
indexofmin = index;
}
return indexofmin;
}
//a배열과 그것에대한 크기와 taget값을 받아준다.
//target과 동일한 a[]배열의 index값을 찾아준다.
int search(int a[], int numberused, int target)
{
int index = 0;
bool found = false;
//a[index]값=target과 동일한 값이되면 루프를 멈춘다.
while ((!found) && (index < numberused))
{
if (target == a[index])
found = true;
else
index++;
}
if (found)
return index;
//else가 나오는 경우는 a[]의 배열이 끝나도록 target과 동일한 값이 없을때 이다.
else
return -1;
}
구조체이다.
C/C++ 언어는 기본 데이터형을 최소화하기 위해서 많이 사용하는 형식의 데이터형만을 기본 데이터형 으로 정의하고 나머지는 프로그래머가 데이터형을 정의해서 사용할수 있도록 만들어졌는데 이를 구조체를 통해 정의한다.
structure안에 정의된 변수를 membervaluable이라 한다.
membervaluable 의 순서는 중요하지 않다.
선언방법 struct <구조체명> { membervaluable 선언; };
예제)
{.cpp}
#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;
struct Theater
{
string strName;
int nTripTime;
int pShowTime[10];
};
int main()
{
Theater aTheater;
aTheater = { "Lotte Cinema in Gunkuk Univ station",10,{9,10,11,13,14,15,16,18,20,23} };
cout<<aTheater.strName<<endl<<"The time from home to the cinema : "<< aTheater.nTripTime << "minutes"<<endl<<"Movie : kings man2";
for (int i=0; i < 10; i++)
{
cout << endl << i+1 << "- showing time : " << aTheater.pShowTime[i]<<":00";
}
return 0;
}
static,vector(벡터) (0) | 2017.12.25 |
---|---|
클래스 생성자 (0) | 2017.12.25 |
array(배열) (0) | 2017.12.25 |
영화 별점 표본 검사 코드 (0) | 2017.12.25 |
assert,debug,driver,stub,중단점,디버그,조사식 (0) | 2017.12.25 |