안녕하세요, 코딩도장에서 C언어를 공부하고 있는 학생입니다.
공부를 하던 도중 질문하고 싶은 것이 2가지 있어서 글을 남기게 되었습니다.
1. 47.6 심사문제 N-gram 만들기
#include <stdio.h>
#include <string.h>
int main()
{
int n;
char word[11];
scanf("%d %s",&n,word);
if(strlen(word)<n)
{
printf("wrong\n");
}
else
{
for(int i = 0 ; i < strlen(word)-(n-1) ; i++)
{
printf("%c%c%c\n",word[i],word[i+1],word[i+2]);
}
}
return 0;
}
이 문제는 처음에 이렇게 작성했는데, 틀렸다고 하더군요
그래서 마지막 반복문을
문의한 코드는 삭제합니다.
이렇게 수정하니, 정답으로 처리되더군요
여기서 궁금한 점은 위 차이점이 정답의 유무를 가질정도의 차이인지,엄연히 다른게 존재하는지 궁금합니다.
2. strcat 질문
strcat에서 두번째 인자(붙일 내용의 문자열)가 NULL값이 된 경우,
세그먼트오류(코어 덤프)가 발생했습니다.
이 오류가 뭔지 찾아보니 건드리면 안되는, 범위 밖의 메모리를 포인터로 건드린 그런 종류의 오류더군요
NULL값과 이 오류가 발생한 연관성을 찾아봐도 도저히 모르겠어서 질문드립니다.
아래는 오류가 발생한 코드입니다. (참고로 47.5 심사문제입니다.)
아래 코드에 입력값으로 "nurs es run"를 입력했을 때 기준입니다.
(주석처리한 부분을 주석해제하면 오류가 발생합니다.
원래는 while(token[count]!=NULL)대신 주석처리된 for문으로 풀려고 했습니다)
*참고로 제 코딩 환경은 replit.com이라는 클라우드 환경입니다.
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
int main()
{
char inputword[31];
char *token[10]={NULL,};
char nospaceword[31]={NULL,};
bool issame = true;
scanf("%[^\n]s",inputword);
char *ptr = strtok(inputword," ");
int count = 0;
while(ptr!=NULL)
{
token[count]=ptr;
count++;
ptr = strtok(NULL," ");
}
count = 0;
while(token[count]!=NULL)
{
strcat(nospaceword,token[count]);
count++;
}
//strcat(nospaceword,token[3]);//이거 왜 오류나는지 찾아보자. NULL값이 scr라서 코어덤프난 것 같은데 확실한지는 모름..
// for(int i=0;i<strlen(token);i++)
// {
// strcat(nospaceword,token[i]);
// }
// ↑오류는 범위 밖 메모리 건드려서 나는 코어덤프. NULL 값 섞여서 일어난 문제같음.
for(int i=0;i<(strlen(nospaceword)/2);i++)
{
if(nospaceword[i]!=nospaceword[strlen(nospaceword)-1-i])
{
issame = false;
break;
}
}
printf("%d",issame);
return 0;
}
감사합니다!
(도장_ 관리자님이 수정함 - 원문 제출일: 수요일, 20 7월 2022, 11:56 오후)
1번. 숫자 값이 입력됩니다.
4 Beethoven
이렇게 입력하면 4-gram을 해야 합니다.
Beet eeth etho thov hove oven
printf("%c%c%c\n",word[i],word[i+1],word[i+2]);
이렇게 작성하면 3-gram으로 고정이니까 심사를 통과하지 못합니다.
Visual Studio에서 디버거를 쓰는 게 좋습니다.
온라인 환경으로는 디버거 지원이 안 될 겁니다.
예외가 발생한 시점에 아래에 자동 조사식 창이 왼쪽에 보이는데 i가 쓰레기 값이 들어가 있습니다.
아래에 로컬 탭으로 변경해서 보면 count 값도 보이고, ptr 값도 보이죠.
조사식 1 탭으로 바꿔서 값을 하나씩 넣어보면 알 수 있는데 strlen(token)은 동작하지 않습니다. 인수 형식이 맞지 않는다고 되어 있지요.
token은 char **이니까 문자열을 담는 배열 포인터죠. const char *만 가능하니 문자열만 넣으라는 뜻입니다.
strlen(token[0])으로 하면 값을 가져오죠.
nospaceword 값을 구하고, strlen(nospaceword)로 하면 원하는 값을 구해질 겁니다.
다만, 전체 로직을 다시 고민하고, 종이와 펜으로 생각을 정리하고,
코딩은 마지막에 하세요. 주먹구구로 코드만 고치면서 정답이 되길 바란다면 시간만 낭비하게 됩니다.
1번은 제가 순간 예제에만 집중하여 본래 묻는 것을 놓친 것 같습니다..
감사합니다!
2번은 strlen 함수에 알맞은 인자가 아니기 때문에, 저런 오류들이 발생했다는 것은 이해했습니다.
추가로 궁금한 것은 해당 코드에서 for문대신 오류가 없는 while문을 써도 strcat(nospaceword,token[3]);부분을 작성하면 오류가 났는데,
이는 예제를 기준으로 token[3]가 NULL값이라서 발생한 오류인가요?
즉, strcat에서 두번째 인자값으로 들어가는 문자열의 내용이 NULL이면 오류가 발생하는지, 발생한다면 왜 발생하는 것인지 궁금합니다.
(검색해봐도 오류가 발생한다/아니다로 나뉘고 혼란스럽습니다..)
strcat 두 번째 인수에 빈 문자열 또는 NULL을 지정하면 안 됩니다.
컴파일 자체는 할 수 있으나 컴파일 경고가 발생합니다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main(void)
{
char s1[31] = "";
strcat(s1, NULL);
printf("%s\n", s1);
return 0;
}
이렇게 작성하고 테스트해보면
main.c:10:20: warning: null passed to a callee that requires a non-null argument [-Wnonnull] strcat(s1, NULL); ~~~~^이렇게 경고가 발생하는 것을 확인할 수 있습니다.
https://glot.io/snippets/gbyr5t2gjn
자세한 건 여기서 확인하세요. 컴파일러 옵션도 참고하면 됩니다.
C 언어는 컴파일이 된다고 올바른 것이 아닙니다. 프로그래머의 자유에 맡겨져 있으며 이 위험한 동작이 너의 선택이면 존중한다, 하지만 경고는 일으킨다 같은 관점입니다. 반면 이후의 현대 언어들은 어차피 개발자들이 이걸 잘 이해하고 쓰는 것도 아니니까 다 못 쓰게 언어 차원에서 금지한다.
들여쓰기로도 싸우니까 아예 들여쓰기 형식도 정해버린다 -> Go 언어
형변환 같은 것도 틀리면 컴파일로 막아버린다 -> Rust 언어
점점 이와 같이 개발자의 자유도를 막는 방향으로 바뀌고 있습니다.
과거에 C 언어라면 대학교에서나 컴퓨터를 접할 수 있고, 고급 인력이 한정적으로 접근했던 시대였지만, 지금은 초등학생도 접근할 수 있고, 누구나 코딩하는 시대이므로 언어 자체가 방어하는 형태로 발전하고 있습니다.
C 언어로 처음 시작하면 자유도가 높아서 엉뚱하게 코딩하고, 경고가 발생하지만, 컴파일이 되는데? 왜 이게 틀린거야? 하는 질문이 많습니다. 하지만 자바, Go, Rust 등의 언어로 갈수록(뒤로 갈수록 나중에 나온 언어) 컴파일 자체가 안 되는 게 많습니다. 비부호 정수, unsigned 자료형을 제대로 이해하고 쓰는 애들이 없네? 자바는 unsigned는 없애라~ signed int만 쓴다... 이게 제임스 고슬링이 자바를 만들 때의 철학이었습니다. C/C++을 보니까 애들이 포인터도 이해 못하는구만? 포인터 없애라.
다중 상속도 이해 못하네? 다중 상속도 없애라. 그래서 자바는 하나의 클래스만 상속할 수 있습니다.
들여쓰기, 코드 포매팅으로 맨날 싸운데? 다 통일해 -> Go 언어
Go 언어는 들어쓰기나 코드 포매팅이 달라고 컴파일하기 전에 소스 코드 들여쓰기를 싹 수정해버리고 컴파일합니다.
뭔 애들이 문자열 변환도 이렇게 못한데? 못하면 컴파일도 해주지마 -> Rust 언어
C 언어에서 컴파일러 경고가 발생하면 잘못된 거라고 생각하고, 컴파일러 경고가 없게 코드를 작성하세요. -> 이건 현재 표준입니다. 컴파일러 경고가 발생하면 보안에 문제가 될 여지가 크고, 무조건 에러로 간주하는 정책도 있습니다.
인터넷 검색해서 된다/아니다로 싸우는 건 의미 없습니다.
컴파일러 경고가 발생하면 경고가 발생하지 않게 해야 합니다.
된다는 쪽은 컴파일이 되니까 된다겠지만,
보안과 코드 안전을 생각하면 경고 발생 = 안 된다로 생각하는 게 맞습니다.
그리고 현대 언어는 아예 컴파일 단계에서 막는 방향으로 발전하고 있습니다. 이는 컴파일러 학문에 발전하고, 구문 분석이 발전하면서 좀 더 정교하게 코드 분석을 수행하고 기계어로 변환할 수 있게 되면서 가능해진 것이기도 합니다. C 언어를 개발하던 당시에는 하고 싶어도 할 수 없었던 현실적인 한계가 있었다는 뜻입니다. 지금은 C 언어 컴파일러도 발전해서 문제의 소지가 있는 것들은 경고로 알려줍니다. 옛날 컴파일러라면 경고도 없었습니다.
궁금하면 80년대 Turbo C Compiler를 받아서 테스트해보면 됩니다.
https://www.c-lang.thiyagaraaj.com/c-downloads/c-compilers/turbo-c-compiler
16비트 환경이므로 sizeof(int)의 결과는 2입니다. 요즘은 sizeof(int)가 4이죠.
대신 온갖 경고도 나오지 않습니다. 불친절한 메시지(또는 메시지가 아예 없음)를 보면서 디버깅해보면 현대 C 언어 컴파일러도 많이 발전했음을 알 수 있습니다.
무료로 공개된 컴파일러이므로 무료로 사용할 수 있습니다. 마지막 버전은 1992년에 개발되었습니다.
정말 자세한 설명 너무 감사드립니다. 정독했습니다.
무료로 이렇게 학습할 수 있는 것도 놀라운데
이해를 돕는 친절한 글까지.. 프로그래밍 공부는 코딩만이 아니라
이런 배경지식과 개발철학, 사고력 등등 폭넓게 공부해야함을 다시 깨닫게 되었습니다.
앞으로도 열심히 공부해서 좋은 자세를 가진 개발자가 되도록 노력하겠습니다. 감사합니다 도장 관리자님.
좋은 하루 되시고, 뜻하신 바 꼭 이루시길 바라겠습니다!