본문 바로가기
  • PunkComputing
공부방

random pwnable

by Mr.DOT 2020. 12. 20.
반응형

rand()

이전 강의가 너무 길어져서 약간 잘라내고 새로운 부분을 추가했다. 여기서는 자료형을 다루는 연산자와, 변수의 개념에 대응하는 상수(Constant)를 강의하려 한다.

 

1. 연산자 (Operator)

 

 

 

 

우선순위별로 보면 다음과 같다

1. 소괄호 () : 수학에서 괄호 속을 먼저 연산하는 것을 떠올리면 된다.

 

2. 단항연산자 : 1개의 항에 대하여 연산을 해준다.

 2-1) 논리연산자 ! : 피연산자 a의 값이 거짓(0)이면 참(1)을, 그 외의 수는 거짓(0)을 반환한다. 사용법 [ !a ]

 2-2) 비트연산자 ~ : 피연산자 a를 bitwise(비트 반전) 시킨다. 즉, 1100 -> 0011. 사용법 [ ~a ]

 2-3) 증감연산자 ++, -- : 피연산자의 앞에 오냐(전위증가), 뒤에 오냐(후위증가)에 따라 의미가 약간 달라진다.

    ++a (전위증가) 의 경우 a를 1 증가시키고 나머지 연산을 시행하는 반면

    a++ (후위증가) 의 경우 연산을 모두 마친 후, 최종적으로 a를 증가시킨다.

 

    ※ 후위증가가 전위증가보다는 우선순위가 높다. 사용법 [ ++a, --a, a++, a-- ]

 2-4) sizeof(변수, 자료형) : 입력받은 변수 혹은 자료형의 크기를 byte 단위로 반환한다. 사용법 [ sizeof(int), sizeof(a) ]

 2-5) casting연산자 (자료형) : 명시적으로 자료형을 변환해준다. 낮은 곳에서 높은 곳으로 형변환(casting)이 될 때는 데이터 손실이 일어나지 않지만, 높은 곳에서 낮은 곳으로 형변환이 될 때에는 데이터 손실이 일어난다.

   (double)3 : 3이라는 숫자는 정수형이지만, double로 형변환 하여 3.0과 동일한 가치를 지닌다. 상위형변환 이므로 데이터 손실이 일어나지 않는다.

   (int)3.5 : 3.5라는 숫자는 실수형이지만, integer로 형변환 하여 3이라는 가치를 지닌다. 하위형변환 이므로 데이터 손실이 일어난다.

 

3. 산술연산자 : 뺄셈, 곱셈, 나머지연산 - 덧셈, 뺄셈 순으로 수학과 동일하다.

 a+b, a-b, a*b, a/b : 각 연산에 대한 결과 반환. 단, 5/2의 경우 2.5가 아니라 2가 된다. (int / int = int가 되는데 2.5는 double형이므로 하위형변환이 묵시적으로 일어남)

 a%b : a를 b로 나눈 나머지 값을 반환한다. 즉, 123%10 = 3이다.

 

4. 비트이동 연산자 <<, >> : a의 비트를 <<(좌측), >>(우측)으로 b만큼 비트를 이동한다. 밀려나는 값들은 사라지고, 새로운 자리에는 0이 들어온다.

 a << 2 : a의 비트가 00001110 이면 00111000이 된다.

 

 ※ 비트를 왼쪽으로 한 번 이동하는 것은 2를 곱하는 효과, 오른쪽으로 한 번 이동하는 것은 2로 나누는 효과를 가져온다.

 

5. 관계연산자 : 다음 식이 참이면 1을 반환, 거짓이면 0을 반환한다.

 a<b, a>b, a<=b, a>=b, a==b (같다 라는 표현은 =을 두개 사용하여 나타낸다), a!=b (a와 b가 다르다)

 

6. 비트논리연산자 : and, or, xor의 논리값을 반환한다.

 a&b : a와 b가 둘 다 참(1)일 때만 1을 반환한다. 그 외에는 0

 a|b : a와 b가 둘 다 거짓(0)일 때만 0을 반환한다. 그 외에는 1

 a^b : a와 b가 서로 다를 때 1을 반환한다. 그 외에는 0

 

 n bit에 대하여 n 비트 전부를 개별로 검사한다. 그리고 그 값을 개별적으로 반환하여 합하여 계산한다. 

즉, 11000000 & 01000001 = 01000000으로 반환한다. 이는 특정 비트를 검사할 때 매우 유용하다. 특정 비트가 검사된다면 왼쪽 예와 같이 0이 아닌 값을 반환하기 때문이다. 가령 예를 들어 하위 두번 째 비트가 1인지 아닌지 여부를 검사한다고 가정하자. 그러면

& 00000010을 주면 하위 두번 째 비트가 1일 경우에만 0이 아닌 수가 반환된다.

 

11111101 & 00000010 = 00000000 (거짓값 : 하위 두번 째 비트가 1로 세팅되어 있지 않다)

11100011 & 00000010 = 00000010 (참 값 : 하위 두번 째 비트가 1로 세팅되어 있다)

11000000 & 00000010 = 00000000 (거짓값 : 하위 두번 째 비트가 1로 세팅되어 있지 않다)

 

7. 논리연산자 : and, or의 논리값을 반환한다. 이 때에는 피연산자 자체가 0이 아닌 경우 참, 0인 경우 거짓을 나타낸다.

 a&&b : a와 b가 둘다 참일 때만 1을 반환한다. 11111101 && 00000010의 경우 둘 다 0이 아닌 수라 참의 값을 지니므로 결과는 참이 된다. 주로 관계연산자와 같이 사용된다. (a>=b) && (a>100) 와 같이 사용한다.

 a||b : a와 b가 둘다 거짓일 때만 0을 반환한다.

 

8. 조건연산자 : a? b: c 의 꼴이며, a가 참이면 b, 거짓이면 c를 반환한다.

 int max = (a>b)? a: b; 의 형태로 사용된다 // a가 b보다 크면 a, 작으면 b가 반환되서 max라는 변수에 할당이 된다.

 

9. 할당 및 복합 할당연산자 (오른쪽에서 왼쪽으로)

 a = b :  위에서 a와 b가 같다라는 연산자는 ==를 사용했는지 알 수 있을 것이다. b의 값을 a에 할당한다.

 a += b : a = a+b 와 동일한 의미를 지닌다. 즉, 현재 a값에 b값을 더해 다시 a에 할당하라는 의미.

 a -= b : a = a-b 와 동일

 a *= b : a = a*b 와 동일

 a /= b : a = a/b 와 동일

 ...

 

10. 콤마 연산자 : 성격이 동일한 자료형을 나열할 때 쓰인다.

 int myInt, yourInt, hisInt, herInt, .... ;

 

 

 

이 모든 것을 어떻게 외우느냐? 하다보면 된다. 진심이다(...)

 

 

 

 

2. 상수(Constant)

 

 

리터럴(literal)이라고 한다. 자기 표현 자체가 값이 되는 것을 의미한다. 가령 예를 들면, int myInt = 1; 에서 1과 같은 것이다.

여기에는 여러 가지 종류가 있는데, 대표적으로는 다음과 같다.

 

접두어 : 특별한 선언이 없으면 10진수이다. 0x로 시작하면 16진수, 0으로 시작하면 8진수로 인식된다.

접미어 : 특별한 선언이 없으면 정수 / double형 실수로 인식한다. 즉, float myFloat = 1.234; 와 같은 경우, 우변의 1.234가 double형으로 인식되기에 하위형변환이 일어나 데이터가 손실 될 수 있다는 경고메세지가 뜰 것이다. (안뜨는 컴파일러도 있다.)

이럴 때, 명시적으로 float myFloat = 1.234f 혹은 1.234F 와 같이 float형을 명시해주면 경고메세지가 사라진다.

 

접미어로 L, l(이건 대문자 i와 상당히 유사하기에 보통 쓰이지 않는다)을 명시하면 long 타입의 정수가 된다. 

long int myLongInt = 1L;

 

 

혹은 const로 선언하는 변수는 심볼릭 상수(Symbolic Constant)가 된다. 이 경우, 이 변수는 더 이상 값을 수정할 수 없고, 초기값으로 지정되는 값을 상수로 가진다. 즉, 초기화 하지 않으면 쓰레기로 초기화가 되고, 값을 변경할 수 없다.

 

변수를 선언하는 형식 앞에 const를 추가한다. const double PI = 3.141592;

 

 

 

마지막 방법으로는 #define이라는 매크로를 이용하는 방법이 있다.

#define PI 3.1415 와 같이 선언한다. ( #include <stdio.h> 가 있는 위치에 선언한다 ). 컴파일러에 의해 타입은 자동으로 정해지므로 특별히 신경쓸 일은 없다. 보통 #define MAX 100 와 같은 용도로 선언하고, 프로그램을 수정할 일이 있으면 맨 윗줄의 #define문만 수정하면 되므로 매크로를 사용하지 않았을 때 보다 더욱 간결하다.

 

 

예를 들어, 다음과 같은 프로그램이 있다고 가정한다. (의미는 생각하지 않아도 된다)

 

#include <stdio.h> #include <stdio.h>

#define MAX 100

 

void main(){ void main(){

int i, j, arr[MAX][MAX]; int i, j, arr[100][100];

for(i=0; i<MAX; i++){ for(i=0; i<100; i++){

for(j=0; j<MAX; j++){ for(j=0; j<100; j++){

printf("%d", arr[i][j]); printf("%d", arr[i][j]);

} }

printf("\n"); printf("\n");

} }

} }

 

 

여기서 크기를 100에서 200으로 바꾸고 싶다고 할 때, 왼쪽의 코드는 #define MAX 200으로만 바꾸어 주면 되지만, 오른쪽의 코드는 100이 들어간 부분을 전부 다 찾아서 고쳐야 한다. 코드가 간결할 때는 별 문제가 없지만, 수 백, 수천줄의 코드를 모두 바꾸기란 여간 귀찮은 일이 아니며, 행여 하나라도 수정을 못하면 예상치 못한 결과가 나올 수 있다. 

 

순수하게 치환되는 방식을 취하므로 다음과 같이 임의로 자신만의 상태(statement)들을 만들 수 있다.

 

#define A_NONE 1000

#define A_KOR 1001

#define A_JPN 1002

#define A_CNA 1003

...

 

 

...

 

int myNation = A_KOR;

if( myNation == A_JPN ) myNation = A_KOR;

 

 

물론 이 경우에 한해서는 #define 대신에 int A_NONE = 1000; 으로 선언하고 똑같이 작성해도 상관없지만, 위에도 말했다시피 #define으로 선언된 것의 자료형은 컴파일러가 알아서 결정해주고, 변수선언으로는 불가능한 작업도 #define은 가능하게 해준다. 이는 나중에 매크로만 따로 강의할 때 자세히 설명하려 한다.

반응형

'공부방' 카테고리의 다른 글

C/C++ 출력_타입  (0) 2020.12.20
FFFFFFFF16  (0) 2020.12.20
tar 사용법  (0) 2020.12.17
2021년 리눅스에서 가상머신환경 만들기 VMware 문제해결  (0) 2020.12.17
ASCII  (0) 2020.11.27

댓글