실무에서도 사용하는걸 봤으니까 알아둡시다.

 

#연산자나 ##연산자는 매크로에서 사용하는 연산자다.

 

# : 연산자는 스트링화 연산자이다.
#은 매크로 함수에 사용되어 매크로 함수의 파라미터를 스트링으로 만드는 역할을 한다.

#include <iostream>
using namespace std;

#define STRING(s) #s

int main() {
	cout << STRING(hello world) << "\n"; // "hello world"로 바뀜.

	return 0;
}

 

출력

 

 

 

 ## : 토큰(token) 연결 연산자이다.
 ##은 매크로 함수에 사용되어 매크로 함수의 파라미터들을 결합하여 하나의 토큰으로 만드는 역할을 하는 연산자다.

#include <iostream>
using namespace std;

#define token(i,j) i##j
#define string(i) lpsz##i

int main() {
	int i = 1, j = 2, ij = 3;

	char lpszStr1[] = "hello";
	char string(Str2)[] = "world"; //

	cout << i << " " << j << " " << token(i, j) << "\n"; // token(i, j)는 ij로 바뀜
	cout << lpszStr1 << "\n";
	cout << string(Str1) << "\n"; // string(Str1)은 lpszStr1로 바뀜
	cout << string(Str2) << "\n"; // string(Str2)은 lpszStr2로 바뀜 

	return 0;
}

 

출력

 

코드 영역에 저장되는건지 데이터 영역에 저장되는건지 말들이 다 다르다.

찾아봅시다..

 

 

VS 2017 이상에서는 리터럴을 char* 가 가리킬 수 없습니다. 반드시 const char* 가 가리켜야 하며, 덕분에 리터럴을 수정하는 괴랄한 짓을 컴파일 단에서 막을 수 있습니다.

 

아래  소스 코드는 VS2015로 한거다.

 

코드 영역인지 확인해보자

마우스를 가져다 대보면 const char[7]로 되어있다.  문자열 상수라고 볼 수 있다.

 

어셈블리어를 한번 살펴보자 

"abcdef"의 주소가 0468BDCh인가 봅니다. 한번 찾아가봅시다.

어디 영역인거지..??

 

 

여러 글들이나 문서들을 거르고 걸러서 분석하고 연구해본 결과

 

 

read only part of data segment 즉 rodata에 저장된다는 말이다.

 

 

 

 

 

 

----------------------------- 찾아본 곳들 -------------------------------

 

en.wikipedia.org/wiki/Data_segment

 

Data segment - Wikipedia

In computing, a data segment (often denoted .data) is a portion of an object file or the corresponding address space of a program that contains initialized static variables, that is, global variables and static local variables. The size of this segment is

en.wikipedia.org

myfreechild.tistory.com/entry/%EB%A9%94%EB%AA%A8%EB%A6%AC%EC%97%90-%ED%95%A0%EB%8B%B9%EC%9D%B4-%EB%90%98%EC%A7%80%EB%A7%8C-%EA%B0%92%EC%9D%84-%EB%B3%80%EA%B2%BD%ED%95%A0-%EC%88%98-%EC%97%86%EB%8A%94-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%98%81%EC%97%AD

 

 

메모리에 할당이 되지만 값을 변경할 수 없는 메모리 영역?

다음은 어떤 데브피아 유저가 질문한 내용에 제가 답변한 글입니다. ------------------------------------------------------------------------------------- 제가 보고 있는 c 언어 책의 포인터와 문자열 부분..

myfreechild.tistory.com

 

 

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

 

www.geeksforgeeks.org/storage-for-strings-in-c/

 

Storage for Strings in C - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

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

 

codingdog.tistory.com/entry/c%EC%96%B8%EC%96%B4-%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%A6%AC%ED%84%B0%EB%9F%B4-%EA%B0%92%EC%9D%B4-%EB%B3%80%EA%B2%BD%EB%90%98%EB%A9%B4-%EC%95%88-%EB%90%9C%EB%8B%A4

 

c언어 문자열 리터럴 : 값이 변경되면 안 된다.

 오늘은 간단하게 문자열 리터럴만 올리도록 하겠습니다. "chogahui", "xi" 이런 것들을 우리는 리터럴이라고 합니다. 그러면, 이것들은 어디에 어떻게 저장이 되길래, 이들을 바꾸려고 하면 RTE가

codingdog.tistory.com

 

 

jayy-h.tistory.com/6

 

문자열 리터럴: char* vs char []

문자열 리터럴이란 무엇일까? 다음은 문자열 리터럴을 설명해주는 좋은 예시이다. std::cout << "Hello World" << std::endl; 위 문장에서 "Hello World" 가 문자열 리터럴에 해당한다. 본 질문에 앞서 다음의

jayy-h.tistory.com

 

softwareengineering.stackexchange.com/questions/294748/why-are-c-string-literals-read-only/294750

 

Why are C string literals read-only?

What advantage(s) of string literals being read-only justify(-ies/-ied) the: Yet another way to shoot yourself in the foot char *foo = "bar"; foo[0] = 'd'; /* SEGFAULT */ Inability to elegantly

softwareengineering.stackexchange.com

 

ssinyoung.tistory.com/m/15?category=810263

 

11. [C / C++] 문자열 상수와 포인터

1. 문자열 상수(리터럴) 문자열 상수는 "HelloWorld"와 같이 프로그램 소스 안에 포함된 문자열을 의미합니다. (리터럴 : 소스코드의 고정된 값을 대표하는 용어 정수, 부동 소수점 숫자, 문자열 등등

ssinyoung.tistory.com

 

 

 

 

메모리에 할당이 되지만 값을 변경할 수 없는 메모리 영역?

다음은 어떤 데브피아 유저가 질문한 내용에 제가 답변한 글입니다. ------------------------------------------------------------------------------------- 제가 보고 있는 c 언어 책의 포인터와 문자열 부분..

myfreechild.tistory.com

 

 

메모리에 할당이 되지만 값을 변경할 수 없는 메모리 영역?

다음은 어떤 데브피아 유저가 질문한 내용에 제가 답변한 글입니다. ------------------------------------------------------------------------------------- 제가 보고 있는 c 언어 책의 포인터와 문자열 부분..

myfreechild.tistory.com

kldp.org/node/155779

 

C언어 주로 constant와 관련된 질문입니다. | KLDP

1) constant와 literal의 관계는? 상수가 리터럴을 포함하는 개념으로 이해하고 있습니다. 따라서 상수는 항상 리터럴이 될 수 있지만, 리터럴은 상수가 될 수 없는게 맞는건가요? 참고로 프로그램 소

kldp.org

 

 

melkia.dev/ko/questions/164194?page=3

 

segmentation-fault - 문자열 리터럴로 초기화 된 "char * s"에 쓸 때 분할 오류가 발생하지만 "char s []"가

...

melkia.dev

 

stackoverrun.com/ko/q/1201298

 

c - 메모리에서 문자열 리터럴은 어디에 있습니까? 스택/힙?

가능한 중복 : C String literals: Where do they go? 내가 아는 한 , 일반적으로 , 포인터의 malloc()에 의해 할당 에 있고, 힙 을 할당합니다 , free()에 의해 할당되지 않는다. 및 비 포인터 (INT, CHAR, 플로트, 등

stackoverrun.com

 

stackoverflow.com/questions/2245664/what-is-the-type-of-string-literals-in-c-and-c

 

What is the type of string literals in C and C++?

What is the type of string literal in C? Is it char * or const char * or const char * const? What about C++?

stackoverflow.com

 

 

 

modoocode.com/33

 

씹어먹는 C 언어 - <15 - 3. 일로와봐, 문자열(string) - 문자열 지지고 볶기 & 리터럴>

 

modoocode.com

 

perfectacle.github.io/2017/02/09/C-ref-004/

 

(C/C++) 참고용 정리 - 메모리 영역(Code, Data, Stack, Heap)

프로그램을 실행하게 되면 OS는 메모리(RAM)에 공간을 할당해준다.할당해주는 메모리 공간은 4가지(Code, Data, Stack, Heap)으로 나눌 수 있다. 이미지 출처: C언어의 메모리 구조 Code우리가 작성한 소스

perfectacle.github.io

 

 

ko.wikipedia.org/wiki/%EB%8F%99%EC%A0%81_%EB%A9%94%EB%AA%A8%EB%A6%AC_%ED%95%A0%EB%8B%B9

 

동적 메모리 할당 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 동적 메모리 할당 또는 메모리 동적 할당은 컴퓨터 프로그래밍에서 실행 시간 동안 사용할 메모리 공간을 할당하는 것을 말한다. 사용이 끝나면 운영체제가 쓸

ko.wikipedia.org

 

 

 

 

visual studio 2015, 2017로 실험(32, 64bit)

122개까지 else if문을 사용할 수 있다.

if와 else문까지 포함하면 124줄이 되겠지..

 

 

else if문이 122개가 초과되면 컴파일러 한계 에러가 뜬다.

어떤 이유때문에 컴파일러 한계가 뜰까?? 왜??

 

docs.microsoft.com/ko-kr/cpp/error-messages/compiler-errors-1/fatal-error-c1061?view=msvc-160

 

심각한 오류 C1061

자세한 정보: 심각한 오류 C1061

docs.microsoft.com

컴파일러 한계 : 블록이 너무 많이 중첩되었습니다.

코드 블록의 중첩이 중첩 수준 한계인 128을 초과합니다. 이것은 32비트 및 64비트 도구 집합 모두에서 C 및 C++에 대한 컴파일러의 하드 한계입니다. 중첩 수준의 수는 범위 또는 블록을 만드는 수만큼 늘릴 수 있습니다. 예를 들어 네임스페이스, using 지시문, 전처리기 확장, 템플릿 확장, 예외 처리, 루프 구문 및 else-if 절은 모두 컴파일러로 표시되는 중첩 수준을 높입니다.

이 오류를 해결하려면 코드를 리팩터링해야 합니다. 어떤 경우든 많이 중첩된 코드는 이해하고 예측하기 어렵습니다. 중첩 수준의 수를 줄이기 위해 코드를 리팩터링하면 코드 품질을 개선하고 유지 관리를 간단하게 할 수 있습니다. 많이 중첩된 코드를 원래 컨텍스트에서 호출된 함수로 나눕니다. 블록 내에서 루프 또는 연결된 else-if 절의 수를 제한합니다.

 

MyStruct의 구조체 사이즈는 몇일까?

 

int형은 4byte, char형은 1byte, short형은 2byte여서

총 7byte라고 생각하는가?

 

정답은 8byte다.

 

왜 그럴까요?

 

구조체는 구조체 변수들 중에 제일 큰 사이즈의 자료형을 기준으로 메모리가 늘어난다.

int형이 제일 큰 사이즈이므로 4byte 단위로 메모리가 늘어난다.

 

메모리를 그림으로 표현하자

c와 s가 추가됨에 따라 3byte가 아닌 제일 큰 사이즈인 4byte로 늘어난 것을 볼 수 있다.

아래와 같을 경우는 어떨까?

제일 큰 사이즈는 int형인 4byte이다.

그럼 4바이트씩 늘어나면 구조체의 크기는 몇일까?

 

아래 사진과 같이 할당돼서 사이즈가 12byte라고 생각하는가?

 

 

정답은 16byte다.

 

왜?

아래와 같이 할당되기 때문이다. 1byte의 c가 할당되고 4byte의 i2가 할당될 때 바로 옆에다 할당되지 않는다.

이렇게 되면 사이에 안 쓰는 메모리가 있으므로 메모리 낭비라고 할 수 있다.

 

그럼 메모리 낭비를 하지 않기 위해 어떤 방법을 이용해야 할까?

 

사이즈가 큰 자료형부터 위에다가 순서대로 선언해줘서 메모리를 아끼자.

 

바꿀 수 있는 방법이 많음

 

임시 변수 이용

int a, b;
int t;
t = a;
a = b;
b = t;

수학적 사고 이용 ㅋㅋ

int a, b;
a = a + b;
b = a - b;
a = a - b;

xor 비트 연산자 이용

int a, b;
a = a ^ b;
b = a ^ b;
a = a ^ b;

 

속도를 비교해봤는데

 

Swap함수를 만들어서 두 변수를 파라미터로 참조하여 함수 안에서 바꾸게 하면 세개 다 속도가 비슷하게 나옴

Main(){
	for( roop ){
    	Sawp(a,b)
    }
}
Swap(int &a, int &b){
	...
}

 

 근데 함수 안쓰고 Main문 안에서 하면 속도차이가 발생함.

Main() {
	for( Roop ){
    	...
    }
}

대략 3000000000회 반복으로 돌렸을 때

임시 변수 : 6~7초

+,- 연산 : 14초

xor 연산 : 14초

정도 나옴.

 

임시변수쓸때는 레지스터와 메모리에 주소값 변경하는걸 내부적으로 하는것보다 
메모리에 있는 변수값 레지스터에 올리고 ALU 연산하는게 속도가 더 느린건가??

그나마 swap함수 안에 넣어놓으면 int t 변수가 swap함수 메모리에 올라가는 동작때문에 비슷한건가????

왜 이런걸까.. 궁금하네

 

암튼 결론 : 임시변수 쓰자.

 

 

디폴트 파라미터의 내부 동작

 

 

컴파일러는 내부적으로 디폴트 파라미터의 모든 조합에 대해 적절한 함수를 생성한다.

함수를 생성할때는 이름 바꾸기(name mangling)를 한 함수들이 생성 될 것이다.

오버로딩과 이와 마찬가지로 동작한다.

 

 

 

함수의 호출 관례(function calling convention)란 함수의 파라미터를 스택에 푸시하는 순서, 푸시하는 쪽 및 이름 변화를 명시한 것이다.

 

변경자 푸시 순서 팝하는 쪽 이름 변화
__cdec 오른쪽에서 왼쪽으로 호출하는 쪽 prepended
__fastcall 왼쪽에서 오른쪽으로 호출당하는 쪽 @ prepended
__pascal 왼쪽에서 오른쪽으로 호출당하는 쪽 Uppercase
__stdcall 오른쪽에서 왼쪽으로 호출당하는 쪽 No change

푸시 순서

위쪽 표의 푸시 순서의 의미는 함수의 파라미터가 어느쪽에서부터 먼저 푸시를 하는지에 대한 말.

 

팝하느 쪽

팝하는 쪽의 의미는 함수 호출 후 종료 시점에서 함수의 스택을 정리할 때 스택의 팝을 어느쪽에서 하느냐에 대한 말이다.

 

이름 변화

- C방식의 경우 명칭 앞에 언더스코어 '_'가 붙는다.

- 레지스터 방식의 경우 @가 붙는다.

- Pascal 바익의 경우 모두 대문자로 바뀐다.

- win32 방식(__stdcall)의 경우 이름은 __stdcall이다.

 

windows의 api 함수는 모두 win32 방식을 이용한다.

모든 C++ 컴파일러에서 기본값은 cedecl이다. 

visual studio IDE에서 calling convention을 바꿀 수 있다.

 

visual c++에서 Windows 프로그래밍을 하더라도 기본 설정은 __cdecl이다.

windows 응용 프로그램의 시작 함수인 WinMain()은 __stdcall이지만 win32 콘솔 응용 프로그램의 시작 함수인 main()은 __cdecl이다.

 

#include <iostream>
using namespace std;

void __pascal f(int i, int j) {
	cout << i << " " << j << "\n";
}

void __cdecl g(int i, int j) {
	cout << i << " " << j << "\n";
}

int main() {
	int i, j;

	i = 1; j = 2;
	f(i == j, i = j);
	i = 1; j = 2;
	g(i == j, i = j);

	return 0;
}

__pascal일 경우 왼쪽에서 오른쪽으로 스택에 푸쉬되므로 "0 1"로 될 것이고

__cdecl일 경우 오른쪽에서 왼쪽으로 스택에 푸쉬되므로 "1 2"로 될 것이다.

 

visual studio ide로 이용 할 경우 컴파일 에러가 발생한다.

windows에서 더 이상 pascal 방식은 존재하지 않는다. 이것은 __stdcall로 대처되었다.

 

 

__fastcall로 바꿨다. 

#include <iostream>
using namespace std;

void __fastcall f(int i, int j) {
	cout << i << " " << j << "\n";
}

void __cdecl g(int i, int j) {
	cout << i << " " << j << "\n";
}

int main() {
	int i, j;

	i = 1; j = 2;
	f(i == j, i = j);
	i = 1; j = 2;
	g(i == j, i = j);

	return 0;
}

이 상태에서 실행을 하면 

__fastcall은 "0 2"이 나와야되고 

__cdecl은 "1 2"이 나와야 된다.

 

근데 막상 실행해보면 둘 다 "1 2"나 "0 2"가 나올 수 있다. 왜그럴까..

 

 

다음과 같은 코드가 있다.

#include <iostream>
using namespace std;

class Something {
public:
	Something() { cout << "2"; }
	~Something() { cout << "~2"; }
};

class Parent {
public:
	Parent() { cout << "1"; }
	~Parent() { cout << "~1"; }
};

class Child : public Parent {
public:
	Child() { cout << "3"; }
	~Child() { cout << "~3"; }

private:
	Something mDataMember;
};

 

 

이렇게 할 경우 정상적으로 소멸된다.

int main() {
	Child c;

	return 0;
}

정상

 

 

아래처럼 동적으로 생성한 파생 클래스 객체를 부모클래스 포인터로 가리키고 있을 경우 소멸 순서가 엉망이 된다.

int main() {
	Parent* ptr = new Child();
	delete ptr;

	return 0;
}

~2, ~3이 호출안됨.

 

Parent타입의 포인터로 delete하면 child의 소멸자가 이닌 Parent의 소멸자가 불러져서 Child의 소멸자와 Child의 데이터 멤버도 삭제가 안됩니다.

 

해결방법은 Parent의 소멸자를 virtual로 만들면 된다.

 

 

부모 클래스에서 virtual을 붙이면 파생 클래스에 자동으로 virtual이 적용되지만..

소멸자를 만들때마다 virtual 키워드를 가져다 붙이는 습관을 만들자.

#include <iostream>
using namespace std;

class Something {
public:
	Something() { cout << "2"; }
	virtual ~Something() { cout << "~2"; }
};

class Parent {
public:
	Parent() { cout << "1"; }
	virtual ~Parent() { cout << "~1"; }
};

class Child : public Parent {
public:
	Child() { cout << "3"; }
	virtual ~Child() { cout << "~3"; }

private:
	Something mDataMember;
};

int main() {
	Parent* ptr = new Child();
	delete ptr;

	return 0;
}

virtual 붙이니까 정상

 

+ Recent posts