Android studio 2.2 버전부터 NDK가 정식으로 지원되기 시작하면서 기존보다 훨씬 더 간단하게 NDK를 이용하여 네이티브 어플리케이션을 작성할 수 있게 되었습니다. 연구실에서 간단한 사용법을 작성하라고 해서 3달 전에 작성했는데, 요즘 바빠서 글쓸 시간도 없고 이거라도 올리자는 생각이 들어서 그 문서를 여기에 올립니다. 이 글에서는 NDK를 이용하는 것에 초점을 맞췄기 때문에 아주 간단한 앱을 만들어 보겠습니다.

Pre – Condition

1.    Android studio 2.2이상이어야 함. (2.1 이하 버전에서도 가능하지만 2.2 버전에서는 NDK가 정식으로 포함되어 매우 편리함.)

2.    SDK Tool이 설치되어 있어야함.

 

1.     Android studio 2.1 이하 버전을 사용하고 있다면 Android studio 2.2 버전으로 업그레이드를 한다.


[Help] – [Check for Updates] Click하여 Android 2.2 버전으로 업그레이드.

2.     SDK tool을 설치한다.

2-1. SDK manager를 연다.


2-1-(a). 프로젝트 화면에서 [Tool] – [Android] – [SDK Manager]를 클릭 하면 SDK manager가 열린다.


2-1-(b). 위의 빨간 박스 아이콘을 누르면 SDK manager가 열린다.


2-1-(c). [File] – [Close Project]를 누르면 나오는 Welcome 화면에서 [Configure] – [SDK Manager]를 누르면 SDK Manager가 나온다.

 

2-2. SDK tools를 클릭하고 오른쪽 하단의 Show Package Details 체크 박스를 체크한다.


2-3. 필요한 Tool들인 LLDB, CMake, NDK를 체크한다.



 

NDK – JNI Test

1.     Welcome 화면에서 [Start a new Android Studio project]를 누르거나 프로젝트 화면에서 [File] – [New] – [New project]를 눌러 project 마법사를 실행한다.

2.     Project 마법사를 실행하면 Android 2.2 Version에서는 2.1까지는 없던 Include C++ Support라는 체크박스가 생긴다. NDK Test 할 것이므로 체크를 하고 Next를 누른다.


3.     다음 화면에서 만들려는 앱의 플랫폼을 선택하고 minimum SDK를 선택한 후 Next를 누른다.


4.     [Empty Activity]를 선택하고 Next를 누른다. Add No Activity를 선택하면 기본 layout도 추가가 안돼서 직접 추가해야 하니 귀찮다. Basic Activity는 별로 원하지 않는 메뉴바 등이 추가되니 Empty Activity를 선택한다.


5.     Activity 이름과 Layout 이름을 입력하고 다음을 누른다.


6.     [Include C++ Support]를 체크 했으므로 Customize C++ support 화면이 나온다. 그냥 Finish를 누른다. 그러면 프로젝트가 만들어진다.

a.    C++ Standard : 사용할 C 언어의 표준을 정할 수 있다. 드롭 다운 메뉴를 보면 Toolchain Default가 있고, C++11을 선택할 수 있다. 만약 cpp에서 Smart pointerThread Class C++11에서 지원하는 기능을 사용하거나 사용할 예정이라면 C++11을 선택하고 아니라면 Toolchain Default를 사용한자. Toolchain Default를 선택하면 default CMake setting을 사용한다. Default로는 32-bit, ARM-based GCC 4.8 toolchain을 사용하는데, Processor Architecture 마다 사용할 Toolchain을 선택할 수 있다. 자세한 내용은 Standalone Toolchain을 참고하면 좋다.

b.    Exception Support : 이 박스를 check하면 C++ exception handling이 가능하다. 이 박스를 체크하면 Android Studiomodule level(일반적으로 [app] level)build.gradle파일에 cmake cppFlags-fexceptions라는 속성을 추가하고 Gradle Cmake에게 저 속성값들을 전달한다.

c.     Runtime Type Information Support: RTTI를 사용하고 싶으면 이 checkbox를 체크한다. 체크 안하고 나중에 필요하다 싶으면 moule level build.gradle파일에 cmake cppFlags-frtti라는 속성을 추가하면 된다. (RTTI는 프로그램 실행중에 클래스의 정확한 타입을 알아내는 기능이다. C++dynamic_cast 등에서 사용된다. RTTI에 대한 자세한 정보를 알고 싶다면 RTTI를 참고하면 좋다.


7.     나오는 프로젝트를 보면 Hello from C++textBox에 출력되는 기능이 구현되어 있는것을 볼수 있다. 이제부터 원하는 기능을 C++로 구현하여 사용하는 방법에 대한 설명이다. 이 예제에서는 간단한 피보나치 함수를 만들어 테스트한다. 왼쪽의 Project Pane에서 app을 클릭하면 일반적인 안드로이드 프로젝트와는 다르게 cpp폴더가 보일 것이다. cpp폴더를 클릭하고 마우스 오른쪽 클릭 [New] – [C/C++ Source File]을 클릭하면 New C/C++ Source File 창이 열린다.



 

8.     Cpp file의 이름을 입력하고 OK를 눌러준다. 아래의 Type의 드롭 다운 메뉴에서 cppheader 중 선택 할 수 있으며, Create an associated header check box를 체크하면 header도 자동으로 만들어진다.


9.     아직 sync가 되지 않았으므로 왼쪽 android project pane에서는 보이지 않는다. 왼쪽의 project paneProject로 바꿔준다.



 

10.  Project로 바꾼 다음 [app] – [src] – [main] – [cpp]를 보면 pibonacci.cpp가 추가된 것을 볼 수 있다. 이제 이 cpp파일을 cmake에게 알려줘야 한다. [app] – [CMakeLists.txt]를 더블 클릭한다.



 

11.  내용을 보면 cmake_minimum_required(VERSION 3.4.1)이 있는데 말 그대로 cmake의 최소 버전을 명시하는 것이다. 우리가 주목할 부분은 add_library() 부분이다. Add_library의 사용법은 다음과 같다.

add_library(<name> [STATIC | SHARED | MODULE]

            [EXCLUDE_FROM_ALL]

            source1 [source2 ...])

<name>에는 library의 이름을 정해주고 두번째 인자는 shared library로 할 지 static library로 할지 정해준다. 두번째 인자는 생략해도 상관이 없다. 그리게 세번째 인자는 source의 경로를 적어준다. CmakeLists.txt 파일이 있는 위치를 기준으로 상대경로로 작성하는 것에 유의 한다. 이 예제 같은 경우에는 add_library 부분을 다음과 같이 수정해준다.

add_library( # Specifies the name of the library.
             native-lib
             # Sets the library as a shared library.
             SHARED
             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp src/main/cpp/pibonacci.cpp)

 

만약 만든 cpp source를 새로운 라이브러리로 만들고 싶으면 다음과 같이 libraryadd_library 함수를 이용하여 추가해주면 된다. 예를 들면 다음과 같다.

add_library(pibonacci-lib SHARED src/main/cpp/pibonacci.cpp)

 

대신에 나중에 자바에서 사용할 때 해당 라이브러리를 System. Loadlibrary()를 이용하여 다음과 같이 해당 라이브러리를 load해줘야 한다.


Add_library 함수에 대하여 더 자세히 알고 싶으면 링크를 클릭한다.


 

12.  CMakeLists.txt 수정을 완료했으면 [Build] – [Refresh Linked C++ Projects]를 누른다.


13.  Project PaneAndroid로 바꾼 다음 [app] – [cpp]를 보면 pibonacci.cpp가 생긴 것을 볼 수 있다.



 

14.  이제 자바에서 native method를 정의해줘야 한다. MainActivity.java에 정의 할 수도 있지만 개인적으로 MainActivity에 이런 저런 method를 정의하는 것을 좋아하지 않기때문에 Pibonacci라는 class를 따로 만들기로 했다. 다음과 같이 자바의 패키지 디렉토리에 Pibonacci라는 자바 클래스를 만들어준다. (androidTest) (test)가 아닌 아무것도 안 써져 있는 디렉토리에 만들어 준다.



15.  다음과 같이 작성을 해준다.

public class Pibonacci {
   
public native static int pibonacci(int input);

   
public static String getPibonacci(int input){
       
return String.valueOf(pibonacci(input));
    }
}

Static을 붙인 이유는 MainActivity에서 객체를 만들지 않고 함수를 콜해주기 위해서 이다. 객체를 만들어서 사용할 거면 static은 생략해도 좋다.

여기서 public native static int pibonacci (int input) 에서 native가 중요한데 저 native는 이 함수가 JNI 함수라는 것을 알려준다. (함수에 대한 자세한 설명은 생략.)

현 상태에서는 pibonacci가 빨간색으로 표시된다. 아직 저 함수를 pibonacci 함수로 구현을 하지 않았기 때문이다. 이제 아까의 pibonacci.cpp로 가서 JNI 함수를 작성해준다. JNI 함수명을 작성할 때는 규칙이 있다.

 Java_Package Name_ClassName_functionName 이다. 패키지 이름의 .은 언더바(_)로 대체해서 써준다.

이 프로젝트의 경우는 Java_com_example_medialab_ndktest_Pibonacci_pibonacci 이다.

헷갈리는 경우는 빨간색 글씨 위에 마우스를 살포시 얹으면 다음과 같이 있어야 할 JNI function name과 함께 오류 메시지가 뜬다. 이제 cpp function을 작성하자.



 

16.  이제 pibonacci 함수를 다음과 같이 구현하면 된다.

#include <jni.h>

extern "C"
jint Java_com_example_medialab_ndktest_Pibonacci_pibonacci(JNIEnv *env, jobject callingObj, jint input){
   
if(input<=1) return input;
   
return Java_com_example_medialab_ndktest_Pibonacci_pibonacci(env,callingObj,input-1)
    +Java_com_example_medialab_ndktest_Pibonacci_pibonacci(env,callingObj,input-
2);
}

함수명을 왜 저렇게 작성하는 지는 전 단계에서 설명을 했다. 여기서 주목해야 할 것은 jint인데 여기서 int 대신에 jint를 써야 함수가 제대로 작동한다. Jint Java Native Method (JNI)의 데이터 타입이다. JNI는 다른 언어로 작성된 코드를 자바에서 호출할 수 있도록 만들어진 규약이다. pibonacci 함수는 Java에서 호출할 용도로 제작하는 것이므로 각 입력 parameterreturn 값은 JNI 데이터 타입을 이용해야 한다. (JNI Data Type에 대해서는 링크를 참조함다.) 함수 안에서는 C/C++ data type을 이용해도 되고, 만약 JAVA에서 호출되지 않고 같은 C/C++함수끼리만 호출되는 함수는 입출력 데이터 타입을 JNI DataType으로 하지 않아도 된다. 대신 입출력이 JNI DataType이 아닌 함수들은 Java에서 사용은 불가하고 같은 C/C++로 구현된 함수에서만 호출이 가능하다. 즉 위의 코드를 다음과 같이 작성해도 완전 똑같이 아주 잘 작동이 된다.

#include <jni.h>

int pib(int input){
   
if(input<=1) return input;
   
return pib(input-1)+pib(input-2);
}

extern "C"
jint Java_com_example_medialab_ndktest_Pibonacci_pibonacci(JNIEnv *env, jobject callingObj, jint input){
   
jint returnVal = pib(input);
   
return returnVal;
}


extern “C”라고 jni를 쓰는 함수 앞에 붙여줘야 한다. 필요시 Block을 지정하여 사용해도 된다. Extern “C”를 붙이는 이유는 우리가 C#같은 언어에서 C/C++ dll 이나 static library를 사용할 때 extern “C”를 붙여주는 것과 같은 이치인 것 같다. 아니면 JNICbase로 해서일 수도 있다. 보통 extern의 의미는 분류를 할 때 external linkage를 갖게 하기 위해서다. , 외부 모듈이 링크를 할 수 있게 해주기 위해서다. 하지만 extern “C”를 하면 name mangling을 하지 않기 때문에 다형성 구현에 문제가 생기는데 이 점에 대해서는 더 조사할 필요가 있다. 어쨌든 다음은 JNI에서 C/C++을 구현하는 각각의 예이다.

Code Example 2-1 Implementing a Native Method Using C

jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (

     JNIEnv *env,        /* interface pointer */

     jobject obj,        /* "this" pointer */

     jint i,             /* argument #1 */

     jstring s)          /* argument #2 */

{

     /* Obtain a C-copy of the Java string */

     const char *str = (*env)->GetStringUTFChars(env, s, 0);

 

     /* process the string */

     ...

 

     /* Now we are done with str */

     (*env)->ReleaseStringUTFChars(env, s, str);

 

     return ...

}

 

 

Code Example 2-2 Implementing a Native Method Using C++

 

extern "C" /* specify the C calling convention */  

 

jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (

 

     JNIEnv *env,        /* interface pointer */

 

     jobject obj,        /* "this" pointer */

 

     jint i,             /* argument #1 */

 

     jstring s)          /* argument #2 */

 

{

 

     const char *str = env->GetStringUTFChars(s, 0);

 

     ...

 

     env->ReleaseStringUTFChars(s, str);

 

     return ...

 

}

JNIEnvJNI interface pointer로써 여러가지 유용한 변환 함수들을 제공한다. 또한 그 다음 오는 jobject 변수는 C/C++에서 this로 생각하면 된다. Python class에서 this를 위해 함수의 parameterself를 넘기는 것과 같다. JNI에 대한 자세한 내용은 링크를 참조한다.


 

17.  이제 MainActivity로 온다. 우리는 native-lib source 추가를 했으므로 따로 더 libraryload할 필요가 없다. 다음과 같이 코드를 작성한다. 그리고 [Run] – [Run app]을 누른다.

public class MainActivity extends AppCompatActivity {

   
// Used to load the 'native-lib' library on application startup.
   
static {
        System.loadLibrary(
"native-lib");
    }

   
@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
        setContentView(R.layout.
activity_main);

       
// Example of a call to a native method
       
TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(
"Pibonacci 10 : "+Pibonacci.getPibonacci(10));
    }

   
/**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
   
public native String stringFromJNI();
}

18.  결과 화면


오늘은 좀 뜬금없지만 엔디언에 대해서 잠시 포스팅을 하려고 합니다.

흠 먼저 엔디안이란 무엇이냐! 바로 연속된 숫자를 배열하는 방법인데요. 크게 빅 엔디안과 리틀 엔디안이 있습니다. 위키피디아 정의를 잠깐 보도록 하죠.

엔디언(Endianness)은 컴퓨터의 메모리와 같은 1차원의 공간에 여러 개의 연속된 대사을 배열하는 방법을 뜻하며, 바이트를 배열하는 방법을 특히 바이트 순서(Byte order)라 한다.

엔디언은 보통 큰 단위가 앞에 나오는 빅 엔디언(Big-endian)과 작은 단위가 앞에 나오는 리틀 엔디언(Little-endian)으로 나눌 수 있으며, 두 경우에 속하지 않거나 둘을 모두 지원하는 것을 미들 엔디언(Middle-endian)이라 부르기도 한다.


출처: 위키피디아 엔디언

그림을 보니 이해가 되죠? 솔직히 우리가 일반적으로 쓰는 컴퓨터는 대부분 리틀 엔디언이랍니다. 그런데 이런 리틀 엔디언과 빅 엔디언이 어떤 차이가 있을까요? 먼저 빅 엔디언은 사람이 숫자를 읽고 쓰는 방법과 같기 때문에 디버깅과정에서 사람이 메모리의 값을 이해하기가 더 편하답니다. 반면에 리틀 엔디언은 메모리에 저장된 하위 바이트들을 컴퓨터가 떼어 내서 쓸때 별도의 연산과정이 없기 때문에 컴퓨터가 연산에 이점이 있죠. 다음 그림을 보면서 마저 이야기를 해 봅시다.

int형 포인터와 char포인터, short포인터를 이용해서 각각 같은 주소를 가리키게 만든 다음, intPtr[0]에 -20을 저장하였다고 가정하여 봅시다. 컴퓨터는 -값을 다룰때 보통 2의 보수로 저장하기 때문에 -20은 11111111 11111111 11111111 11101100 으로 변환하여 메모리에 저장하게 됩니다. 여기서 리틀 엔디안과 빅엔디안이 저장되는 방식을 표에서 한번 보죠. 리틀 엔디안은 바이트를 거꾸로 정렬해서 메모리에 집어넣고 빅엔디안은 우리가 보는 그대로 메모리에 집어넣죠? 그래서 빅 엔디안은 우리가 디버깅하며 메모리 값을 보기에 편한겁니다. 그러나 만약에 우리가 short변수로 -20을 떼어낸 다고 생각해 봅시다. 그렇다면 빅 엔디안은 shortPtr[1]을 떼어내야 -20이 정상적으로 출력 될테고, 리틀 엔디안은 그냥 short[10]를 떼어내야겠죠? 즉 리틀 엔디안은 주소의 시작값부터 스택에 넣어 읽어들이면 되지만 빅 엔디안은 저장되어 있는 범위의 맨 뒤에서부터 역방향으로 읽어야 한다는 이야기 입니다. 각각 장점과 단점이 존재하죠. 근데 사실 오늘날의 프로세서는 여러개의 바이트를 동시에 읽어들여 동시에 계산을 수행하기 때문에 두 엔디언 사이에 사실상 차이가 없다는 군요. 바이엔디언과 미들 엔디언이 또 있는데 간단하게 요약만 하고 넘어가도록 하겠습니다.

바이 엔디언: 빅 엔디언과 리틀 엔디언 둘 중 하나를 선택 할 수 있는 것.

미들 엔디언: 빅 엔디언과 리틀 엔디언 둘 다 사용하는 것.(예를 들면 32비트 정수가 2바이트 단위로는 빅 엔디언이고 그 안에서 1바이트 단위로는 리틀 엔디언인 경우)

다음은 이를 확인해 보는 c++코드 입니다. 여러분의 컴퓨터가 리틀 엔디언이라고 가정하였습니다. 만약에 두번째 줄이 위 그림의 빅엔디언 처럼 나오면 여러분의 컴퓨터는 빅 엔디언인 거겠죠^^

#include <iostream>
#include <bitset>
using namespace std;

int main() {
	int *intPtr = new int [1];
	short *shortPtr;
	char *charPtr;
	*intPtr = -20;
	shortPtr = (short*)intPtr;
	charPtr = (char*)intPtr;
	cout << "-20을 2의 보수로 표현: ";
	for (int i = 3; i >= 0; i--) {
		bitset<8> tempBit;
		tempBit = charPtr[i];
		cout << tempBit << " ";
	}
	cout << endl << "저장되어 있는 비트\t";
	for (int i = 0; i < sizeof(int); i++) {
		bitset<8> tempBit;
		tempBit = charPtr[i];
		cout << i << ":" <<tempBit << "\t";
	}
	cout << endl;
	cout << "int로 4byte를 읽었을때: "<<*intPtr<< endl;

	cout <<"shortPtr[0]: ";
	bitset<8> bitSet0(charPtr[0]);
	cout << bitSet0 << "\t";
	bitset<8> bitSet1(charPtr[1]);
	cout <<bitSet1 << "\t";
	cout << "shortPtr[1]: ";
	bitset<8> bitSet2(charPtr[2]);
	cout << bitSet2 << "\t";
	bitset<8> bitSet3(charPtr[3]);
	cout << bitSet3 << "\t";

	cout << endl << "short로 2byte씩 읽었을때: " << endl;
	for (int i = 0; i < sizeof(int) / sizeof(short); i++) {
		cout << i << "번째: " << shortPtr[i] << "\t";
	}
	cout << endl;

	return 0;
}


이미 아시는 분들도 굉장히 많으시겠지만 혹시나 도움이 될까하여 올립니다. 
아마 visual studio 2010에서는 c코드들이 잘 컴파일이 되고 실행이 되나, visual studio 2012, 2013, 2015에서는 scanf나 fopen등이 컴파일이 되지 않아 고생하시는 분들도 있을거라 생각됩니다.

일단 그 이유부터 말씀드리자면 개발자들이 함수의 오류가 발견되면, 그 함수를 deprecate해버리고 보안버젼의 새로운 함수를 추가해 버리기 때문입니다. 
(ISO에서 c++11 표준을 정하니 ms에서 visual studio 2012부터 바꾼거죠) 
예를들어 scanf의 경우 보안상의 문제로 c++ 98에서 c++11으로 넘어가는 과정에서 deprecated되버리고 scanf_s이라는 보안버젼 함수가 새로 생겨버렸습니다. 
visual studio 2010에서는 잘 컴파일이 되는데 visual studio 2012이상 버젼에서는 정상적으로 컴파일이 안되는 이유는 visual studio 2010에서는 default로 c++98을 사용하고, visual studio 2012이상 버전부터는 c++ 11을 사용하기 때문입니다. 

즉, visual studio 2012 이상 버젼부터는 scanf를 사용하면 warning이 뜨면서 컴파일이 안됩니다. 

scanf는 최근 몇년동안 보안 문제로 말이 있었죠. 뭐 이유를 알아보면 여러가지가 있지만 콘솔 io의 경우 input size를 정해주지 않으면 command를 같이 입력하는 방식으로 공격이 가능하다는데, 제가 보안쪽을 잘 몰라서 뭐라고 말을 더 못하겠군요. 뭐 이부분에 대해선 다른 블로거 님들에게 패스를 하고, 본론으로 다시 돌아가도록 하겠습니다.


이렇게 코드를 쓰고 컴파일을 하면 보통 에러가 나며 컴파일이 안됩니다.

#include <stdio.h>
int main()
{
	int inputNum;
	scanf("%d", &inputNum);
	printf("%d \n", inputNum);

	return 0;
}

이렇게 하고 start하면 아래 그림과 같은 메세지가 뜨면서 컴파일이 되지 않죠.


에러 내용을 해석해보면, scanf라는 함수가 안전하지 않으며, scanf대신 scanf_s라는 새로운 함수를 사용하는 것을 고려해 보라고 나옵니다. 또, deprecation된 함수를 사용하려면 _CRT_SECURE_NO_WARNINGS를 사용하라고 나오네요.(deprecation을 사용하지 않으려면)


그러면 지금부터 이러한 문제를 해결하는 방법을 몇가지 말씀 드리려고 합니다.

1. _CRT_SECURE_NO_WARNINGS를 사용하여 warning을 무시한다.


#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	int inputNum;
	scanf("%d", &inputNum);
	printf("%d \n", inputNum);

	return 0;
}

이렇게 코드의 가장 윗부분에 _CRT_SECURE_NO_WARNINGS를 디파인 해주면 정상적으로 컴파일이 되는 것을 확인할 수 있습니다.

또는 에러 코드를 보면 C4996이라고 나오죠. 이 에러코드를 직접 무시하는 코드를 헤더 윗쪽에 추가 해서 정상적으로 컴파일을 하게 만드는 방법도 있습니다. 좀 자세히 말하자면 우리가 프로젝트를 만들때 SDL 체크를 사용하면 컴파일시 추가적으로 여러가지 보안 검사를 실시 하는데, 검사중에 경고 코드 C4996(사용되지 않는 함수 사용 경고)기능을 사용하지 않도록 설정하는 것입니다.


warning에 대한 링크 : warning-msdn

#pragma warning(disable: 4996)
#include <stdio.h>
int main()
{
	int inputNum;
	scanf("%d", &inputNum);
	printf("%d \n", inputNum);

	return 0;
}

이런식으로요 ㅎㅎ.

pragma에 대해선 나중에 #if, #ifdef등과 함께 나중에 한번 포스팅 하도록 하겠습니다.

2. 대세에따라 secure 버젼의 새로운 함수를 사용한다.

fopen의 경우 주소를 리턴하는게 보안상 문제가 있다고 판명되어 주소를 리턴하지 않고 인자로 받아 설정하는 fopen_s라는 secure버젼의 함수가 새로 만들어졌습니다.

사실 어떠한 함수가 deprecated가 되었다는 것은 sw전문가들이 문제가 있다고 생각되어 deprecated를 시키고 보다 안전한 함수를 새로 만들었다는 것이기 때문에 대세를 따라 secure버젼을 사용하는 것이 좋다고 개인적으로 생각합니다.


#include <stdio.h>

int main()
{
	FILE *data;
	fopen_s(&data, "test.txt", "w");
	fprintf(data, "%s", "Hello world!");
	fclose(data);

	return 0;
}

요런식으로 씁니다. ㅎㅎ


#include <stdio.h>

int main()
{
	char s[16];
	scanf_s("%s", &s, 16);//마지막에 input data size를 적어줍니다.(이게 scanf와 비교하여 바뀐부분이죠^^)
	printf(s);

	return 0;
}

scanf_s의 경우는 이런식으로 씁니다.

뭐 이러한 정보들은 구글링하면 바로바로 나오니 그때 그때 사용법을 검색해서 쓰기 쉽습니다.

유용한 사이트

CPlusPlus Page

MSDN Page

3. Project 생성시 SDL(Security Development Lifecycle) checks 옵션을 체크 해제한다.

이걸 체크해제하고 프로젝트를 만들면 추가적인 오류검사를 수행하지 않습니다. 예를 들어 함수의 반환형을 int로 해놓고 아무것도 반환하지 않아도 컴파일이 되고 실행이 됩니다. 뭐 컴파일이야 되겠지만은 별로 권장하고 싶진 않네요.


#include <stdio.h>

int main() {
	int inputNum;
	scanf("%d", &inputNum);
	printf("%d \n", inputNum);
}

위와 같은 코드도 컴파일이 됩니다.

어쨋든 SDL chechs 해제를 한번 해보죠.

1. 프로젝트를 만들어 줍니다.


2. 다음을 클릭합니다.


3. SDL checks의 체크를 해제해 줍니다.


4. 위의 코드를 한번 실행해 봅니다.

아주 잘 실행이 되는군요 ㅎㅎ



SDL에 대한 /sdl 링크 하나 남기겠습니다.

MSDN SDL

뭐 오늘 포스팅은 여기까지입니다. 마지막 방법은 되도록 쓰지 마세요 ㅎㅎ

'Programming > C/C++' 카테고리의 다른 글

Endian(Little endian, Big endian)  (0) 2015.10.08

+ Recent posts