게시판

검색 결과: 43

 

UNIT 34부터 복습하세요.




영어로 읽는 법 익혀야 합니다. 이 그림은 눈 감고 그릴 수 있어야 합니다.

같은 그림을 책 덮고 10번 그리면 됩니다. 그러면서 완전히 이해하세요.



당연히 이 그림도 눈 감고 그릴 수 있어야 합니다.

그림으로 이해해야 합니다. 코드로 이해하려고 하면 시각적이지 않아서 오히려 어렵습니다.


포인터 사용한 코드 보고 그림으로 그려낼 수 있으면 이해가 된 것.

그림으로 못 그리면 이해를 못 하는 것.


이것도 전혀 이해가 안 된 상태라 그렇습니다.


이런 함수를 만들었습니다.

그러면 호출하는 쪽에서는 이 함수의 인자 형식을 맞춰서 호출해야 합니다.


long long *numPtr이므로

numPtr은 pointer to long long입니다.

함수에 pointer to long long을 전달하고 싶다면 pointer to long long의 주솟값을 전달해야 합니다.

pointer to long long의 주솟값은 어떻게 표현합니까?

address of pointer to long long입니다.

그러면 numPtr의 타입은 이미 pointer to long long이니까 이건 어떻게 하면 address of pointer to long long으로 바꿉니까?

&numPtr이라고 쓰면 됩니다.

&numPtr을 영어로는 address of pointer to long long이라고 읽습니다. 궁금하면 유튜브에 가서 C 언어 포인터 강좌 영어로 된 거

찾아보세요. 이런 표현이 수도없이 나오는 것을 확인할 수 있습니다.

그러면 포인터를 인자로 받는 함수의 인자는 어떻게 선언해야 합니까?

address of pointer to long long == pointer to pointer to long long

입니다.

address of는 주솟값이므로 pointer to와 호환이 됩니다.

따라서 allocMemory에서는 long long **ptr로 선언하면 됩니다.

그런데 allocMemory를 타입에 상관없이 메모리를 할당하게 만들고 싶습니다.

그러면 long 대신에 범용 타입인 void를 씁니다.

void **ptr로 선언합니다.


allocMemory((void**)&numPtr, sizeof(long long));

numPtr의 타입은 무엇입니까? 영어로 대답하세요. 그림도 영어로 그리세요.

pointer to long long입니다.

allocMemory는 void **ptr이므로 &numPtr로 만들어서 타입을 맞춰줍니다.

하지만 void **과 &numPtr은 타입이 일치하지 않습니다.

따라서 (void **)로 타입 캐스팅을 해서 형을 일치하게 맞춰줍니다.


(void **)을 지워봅시다.

컴파일러 경고를 모두 켜고 보면 다음과 같이 경고가 발생합니다.


main.c:14:17: warning: incompatible pointer types passing 'long long **' to parameter of type 'void **' [-Wincompatible-pointer-types]
    allocMemory(&numPtr, sizeof(long long));
                ^~~~~~~
C 언어에서는 경고를 에러로 취급합니다. 경고는 잠재적으로 문제가 있는 코드라는 뜻이고, 보안 문제를 일으키기 쉽기 때문입니다.

incompatible pointer types입니다. 호환 안 되는 포인터 타입이라는 뜻입니다.

따라서 잘못된 코드입니다.

C/C++ 언어에서는 코드를 이렇게 고쳤는데 돌아가는데? 그러면 이렇게 작성해도 되겠네?

--->>> 아주 옛날에 90년대 이전에 개발자들이 흔히 하던 생각이고, 이렇게 잘못 배운 사람들이 성장해서 C/C++ 입문서를 쓰고,

수많은 잘못된 내용을 담은 책들이 탄생했습니다. 베스트셀러에도 여전히 그런 문제가 있고, 그런 분들을 저격하는 블로그 글도

쉽게 찾아볼 수 있습니다.

-. C/C++를 가르치기 전에 생각해 보기, 2023.3.11

https://proofby.ac/teaching-c/


블로그 작성자의 코멘트입니다.

또 UB로 글을 썼습니다
이번에는 UB 이야기만 한 건 아니고, C/C++ 과목을 열거나 책을 쓸 때 확인해 줬으면 하는 내용들을 좀 화난 목소리로 적었습니다


이렇게 외우면 안 됩니다. 이건 타입을 이해한 게 아닙니다.

원리를 이해하세요.

"단일 포인터의 메모리 주소"가 무엇입니까?

long long *numPtr도 단일 포인터이고, &numPtr은 단일 포인터의 메모리 주소일 겁니다.

그러나 한글로 '단일 포인터의 메모리 주소'라고 외우려 하지 말고, &numPtr 타입은

address of pointer to long long으로 영어로 외우세요.

영어로 보면 명확합니다. C 언어 표준에도 이렇게 설명되어 있고, 영어 교재도 다 이렇게 설명합니다.

한국어로 역순 번역해서 이상하게 가르치니까 더 헷갈리고 어렵다고 하는 겁니다.

int *numPtr로 선언했다면 numPtr의 타입은 무엇입니까? pointer to int입니다.

'단일 포인터'라는 표현은 영어로 치면 single pointer인데 C 언어 표준안에는 쓰지도 않는 용어입니다.

double pointer도 마찬가지.


검색 결과로도 알 수 있지만,

pointer to pointer이고, 표준안도 이렇게 쓰고 있습니다. 다만, 사람들이 발음하기 편하게 축약해서 쓰는 표현 정도가

double pointer입니다. 편의상 double pointer라고 쓸 수는 있습니다만, 권장하지 않습니다.

int **ptr이라면 ptr의 타입은 pointer to pointer to int이고,

*ptr로 썼다면 *ptr의 타입은 pointer to int이고

**ptr로 썼다면 **ptr의 타입은 int입니다.

**ptr는 double pointer이고, *ptr은 single pointer라고 생각하는 게 아니라

*를 하나 붙일 때마다 pointer to를 하나씩 소거하면 됩니다.

지금 이 설명을 10번 읽으세요. 그리고 스스로 타입을 소리 내어 말하세요.

그림으로 그리세요.


cdecl.org 사이트에서도 타입을 입력해보세요.

https://cdecl.org/

밑에 영어로 뭐라고 되어 있습니까?

pointer to pointer to int입니다.

포인터가 이해가 안 된다고 베스트셀러 C 언어 책 사서 보지 마십시오.

위 블로그 글에서 저격한 책이 베스트셀러입니다. 블로그 들어가면 어떤 책인지 알 수 있습니다.

잘못된 내용 지적하면 댓글 삭제하고, 차단하고, 그런 베스트셀러는 곤란합니다.

현업에서 가장 많이 하는 일이 잘못 배워온 C/C++ 지식을 교정하는 일입니다.

https://sunyzero.tistory.com/225

교수도 C 언어 제대로 모르는 경우가 많습니다.


이 답변은 사이트 관리자(저자 아님)가 작성했습니다.



, 도장_ 관리자님이 작성
 

main.c:14:2: warning: implicit declaration of function 'scanf_s' is invalid in C99 [-Wimplicit-function-declaration]
        scanf_s("%d", &num);


scanf_s는 비표준이므로 쓰면 안 됩니다. Visual Studio 외에는 지원하지 않습니다. 대다수 온라인 심사는 리눅스 환경이고 GCC나 Clang을 씁니다. 이들 컴파일러는 scanf_s를 지원하지 않습니다.

scanf로 고쳐도 에러는 계속 발생합니다.


main.c:22:5: error: use of undeclared identifier 'i'
                m[i] = malloc(sizeof(int) * num);
                  ^
Visual Studio에서도 공통인데 이 경우엔 그 위에 코드가 문제인 경우도 종종 있습니다.

    for (int i = 0; i < num; i++);
    {

        m[i] = malloc(sizeof(int) * num);

    }


for 문 뒤에 ;이 있습니다. 그러면 블록이 없이 끝나는 한 줄짜리 for 반복문이 됩니다. 그러면 for 블록의 범위는 끝난 것이고, for 문은 종료된 것이므로 for 문 안에서 int i로 선언한 변수 i도 사라집니다. 그게 변수 i는 선언되지 않았다는 오류 메시지의 이유입니다.


이런 식으로 하나씩 살펴보면서 디버깅을 해야 합니다.

UNIT 6 디버거를 사용하거나 포럼 상단에 있는 시각화 사이트를 이용하세요.

UNIT 번호는 38.8입니다. 포럼 오른쪽 상단 검색 상자에 38.8로 검색해서 이전 질문과 답변을 참조하는 것도 방법입니다.

심사문제를 제출했을 때 나오는 해설을 보세요. int **m으로는 숫자 행렬인데, 해설에서는 char **m으로 되어 있습니다. 행렬의 값에는 숫자, '.', '*'이 들어갑니다. 문자와 숫자 모두 들어갑니다. 숫자만 있는 행렬이면 문자를 별도로 처리해야 합니다. 내부적으로 값이 11이면 화면에는 .으로 출력하고, 값이 12이면 *로 출력하는 식의 전처리를 거치는 방식으로 코드를 작성할 수는 있지만, 문제 해결이 어렵겠지요.

그러면 처음 행렬을 입력 받을 때 지뢰 위치로 *이 입력되면 내부에서는 12로 처리해야 한다거나 하는 식의 과정이 추가로 필요합니다. 코드를 더 복잡하게 만드는 요인이 됩니다.

해설을 참고해서 해설에서 제시하는 방법을 이해하면서 코드를 작성하려고 해보세요.



, 이 의진님이 작성
 

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

#include <stdlib.h>


int main()

{

int num;

scanf_s("%d", &num);


int** m = malloc(sizeof(int*) * num);

for (int i = 0; i < num; i++);

{

m[i] = malloc(sizeof(int) * num);

}

for (int i = 0; i < num; i++)

{

for (int j = 0; j < num; j++)

{

if (i == j)

m[i][j] = 1;

else

m[i][j] = 0;

}

printf("\n");

}

for (int i = 0; i < num; i++)

{

free(m[i]);

}

free(m);

return 0;

}

이렇게 코드를 작성 했는데도  계속 오류가 나는데 뭐가 문제인지 모르겠어요

1. void*형식의 값을 사용하여 int **형식의 엔터티를 초기화 할 수 없다고 나오고

2. 식별자 i가 정의 되어 있지 않다고 합니다

도와주시면 감사하겠습니다.

 

범위를 넘어가는 것에 대해서 C 언어는 자유롭습니다.

이로 인해서 다양한 over-write, 쓰기가 가능하고 이는 권한 탈취 등의 보안 버그로 사용됩니다.

strtok_s 같은 함수가 있습니다. 이는 C11에서만 지원합니다.

C 언어는 수많은 레거시가 있고, 여전히 과거에도 지금도 strtok 함수가 쓰입니다.

strtok 함수를 아예 쓰지 말라고 경고하는 것은 Visual Studio가 유일합니다. 그러나 현장에서 리눅스 분야에서 코딩한다면 strtok를 사용한다고 경고가 발생하지는 않습니다. GCC, Clang을 쓰세요. 그러면 경고는 없습니다.

이는 Visual Studio를 만든 MS의 정책일 뿐입니다.

scanf_s 함수는 Visual Studio에서만 지원하고, 비표준 함수라 다른 플랫폼에서는 지원하지도 않습니다. 코딩 테스트는 주로 리눅스 환경에서 이뤄지는데 리눅스 환경에서는 scanf_s 함수를 지원하는 컴파일러가 있는지 모르겠습니다. GCC, Clang에서는 지원하지 않습니다. 코딩 테스트 문제를 모두 풀어 놓고도 0점으로 탈락할 수 있습니다. Visual Studio를 따르지 마세요. 그냥 표준을 따르세요.


https://www.youtube.com/watch?v=WXYBoM20f5g

VS 2022와 큰 차이 없으니 이거 보고 컴파일러를 Clang으로 바꾸는 것도 방법입니다.


, 도장_ 관리자님이 작성
 

Visual Studio는 scanf를 사용하면 경고를 표시하지만,

scanf_s 함수는 마이크로소프트에서만 쓸 수 있는 비표준 함수입니다.

코딩 테스트가 실시되는 리눅스 기반의 시스템에서는 scanf 표준 함수만 사용할 수 있습니다.

다양한 플랫폼에서 호환되는 코드를 작성하려면 scanf 함수를 사용해야 합니다.


 

scanf_s는 마이크로소프트가 만든 비표준 함수입니다.

scanf 표준 함수를 사용해야 합니다.

온라인 코딩 테스트는 리눅스 기반에서 이뤄지고 scanf_s 함수가 없습니다. 입력 단계에서 입력이 안 되고 컴파일이 안 되니 코딩 테스트는 모조리 탈락하게 됩니다.

scanf 경고는 무시하면 됩니다.

scanf는 입력 받은 길이를 반환하는데 반환값을 사용하지 않는다는 경고입니다. 2글자 입력 받았습니다 하는 반환값을 사용할 이유는 없으니까 무시하면 됩니다.


 

강의안에도 나와있는데

scanf가 보안에 취약하다고 하네요

scanf_s로 써보시면 경고 안뜰지도?? 저도 지금 배우는단계라 잘몰라여

, 도장_ 관리자님이 작성
 

UNIT 11.1을 보세요.

해당 단원 학습 안 한 것으로 보입니다.

UNIT 10 학습하고, UNIT 11 건너 뛰고, UNIT 11.5 퀴즈부터 학습하고 있는 것으로 보입니다.

자세히 설명되어 있습니다.

코딩 도장은 순차 학습을 전제로 설계되어 있습니다.

알고 있다고 생략하고 건너 뛰면 누락된 지식이 반드시 발생합니다.

scanf_s는 사용하지 마세요. 비표준이니까 사용하지 않습니다.

입사 시험에서 보는 코딩 테스트에서도 scanf_s 쓰면 코딩 테스트를 통과할 수 없습니다.


 

scanf_s는 Visual C++에서만 동작하는 비표준 함수입니다.

온라인 심사에서는 사용할 수 없습니다(취업 시 이용되는 심사 사이트에서도 이용할 수 없는 함수입니다)

#define _CRT_SECURE_NO_WARNINGS

를 사용하세요.

UNIT 11을 복습하세요.


 

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>


int main()

{

    unsigned char flag = 16;

    unsigned char num1, num2;


    scanf_s("%hhu %hhu", &num1, &num2);

    flag |= num1 << 3;

    flag &= ~(num2 >> 2);

    flag ^=  255;


    printf("%u\n", flag);


    return 0;

}


제가 문제를 잘못 이해한 것 같아요. 분명 제가 생각하는 값은 나오는데 왜 답이 136이 나와야 하는지 모르겠습니다.