[c언어] char 배열에 문자열 저장하기

mrx@Google의 이미지

#include
#include
#include

int main()
{
char str[10]={};

str="hello";
printf("%s\n",str);
}
이대로 하면 컴파일 에러가 납니다.
지금까진 단순히 str은 배열의 이름은 주소가 고정되어 있어서 배열의 이름에 "hello"의 주소값을 저장하는 것은 불가능하다 라고 생각했습니다.
근데 정확한 이유는 사실 str의 자료형이 char[10]이므로 "hello"의 자료형인 char*과 달라서 대입연산이 안되는게 맞나요?

라스코니의 이미지

컴파일 에러 메시지가 구체적인 문제를 알려주지 않았나요?
대부분의 문제는 컴파일 오류 메시지나 간단한 디버그만으로도 해결되는 경우가 대부분입니다.

str="hello";

이라는 코드가 C에서 쓸 수 있는 표현인지, 아니면 C++ String class에서 쓰는 표현인지 생각해 보세요.
희안하게 char str[10] = "hello";는 되는데 비슷한 str="hello";는 허용되지 않습니다.

대신 strcpy()라는 것을 써야 하죠.

세벌의 이미지

kldp에서 소스코드 올릴 때는 code 태그 쓰라는 말.
눈에 잘 안 띄나 봅니다.
컴파일 에러 난다면 메시지를 보여주세요.
원래 C 언어가 쉽지 않은 언어죠.
답은 이미 스스로 찾으셨을 거 같아요.

Stephen Kyoungwon Kim@Google의 이미지

지금까진 단순히 str은 배열의 이름은 주소가 고정되어 있어서 배열의 이름에 "hello"의 주소값을 저장하는 것은 불가능하다 라고 생각했습니다.

부분적으로 맞는 얘기입니다.

근데 정확한 이유는 사실 str의 자료형이 char[10]이므로 "hello"의 자료형인 char*과 달라서 대입연산이 안되는게 맞나요?

아닙니다.

어떤 주어진 operator (이 경우에는 =, assignment operator)가 받아들일 수 있는 operands의 타입의 조합은 정해져 있습니다. 예를 들어 C에서는 int형 배열과 int를 갖고 +를 할 수는 없죠. +가 되는 건 뭔가 비슷하고 산술 연산들이 가능한 타입들, 예컨대 int + int, float + int, char + short 등입니다.

=의 경우에도 마찬가집니다. =가 허용되는 타입들이 정해져 있습니다. char [10] = typeof( "hello" )가 허용되지 않기 때문에 컴파일 에러가 납니다. "hello"의 타입은 char []입니다. char *는 아니구요.

왜 대입이 허용되지 않게 했을까요? 정확하게 말하면 왜 초기화할 땐 대입이 가능한데, 중간에 assign은 안 되게 했을까요? 만에 하나 허용이 되었다면, =가 어떤 효과를 냈어야 할까요? 이런 의문들을 해결하시면 아마도 소위 "진짜 이유"가 나오겠지요.

mrx@Google의 이미지

그럼 자료형이 char[10]과 char[]이라 대입연산이 안된다고 하는게 맞았겠군요.
그럼 안되게 만들어 놓은 이유는 char[10]에 대한 메모리낭비 때문인거 아닌가요?

익명 사용자의 이미지

메모리 낭비 때문이 아니라... 메모리가 할당된 상태에서 대입이 안 되죠. 할당된 메모리에 복사를 해줘야죠
char[0] = 'a';
char[1] = 'b';
char[2] = 'c';
...
이런 식으로 복사를 해주거나.. strcpy() strncpy() 이런 함수로 복사를 해줘야할 거에요

라스코니의 이미지

쓰신 글을 읽어보니 C를 혹시 C++ 과 같이 동작하는 것처럼 생각하시는 것 같네요. C와 C++은 전혀 다른 언어입니다. 감사하게도 C++이 C를 일부 용인해줄 뿐이지요.

char str[10] = "hello";
char str* = "hello";
char str[] = "hello";

아마 위 모두 잘 컴파일 될 것입니다. 이때 우리는 이것을 str*, str[]에 맞추어 복잡하게 생각해야 하느냐? 아닙니다. C는 그냥 모든 것이 str*일 뿐입니다. 친절하신 컴파일러께서 프로그래머가 친숙하게 받아들일 수 있도록 str[]라는 표기를 받아주시는 것 뿐이죠.

이것을 어떻게 컴파일러가 처리하는지 알고 싶다면 리눅스에 컴파일 한후 objdump 등으로 생성된 코드의 data, text, BSS 등의 영역을 보시면 됩니다.

아마 질문하신 분이 C++ 쪽을 먼저 입문하신 것 처럼 보이네요. str = "hello"; 만약 str이 CString 객체였다면 너무나도 자연스러웠을 이 문장이 C에서 용인되지 않는 것은 C에서는 이 표현을 동작하게 하기 위해 포함되어야 할 여러가지 처리들을 지원하도록 되어 있지 않기 때문입니다.

Stephen Kyoungwon Kim@Google의 이미지

array와 pointer는 대부분의 경우는 같고, 프로그래머는 같다고 생각하고 써도 되는 경우가 대부분입니다만, C99에서도 다른 타입인 것으로 알고 있습니다. objdump 말고 중간 코드를 찍는 명령어를 사용해서 보세요. 두 가지가 중간 코드 수준에서도 완전하게 같지는 않습니다. "hello"가 data section에 있으면 alignment가 16의 배수일 필요는 없는 것으로 기억하는데, 나머지 두 경우가 stack이라면 16의 배수여야 할 겁니다.

익명 사용자의 이미지

Quote:
char str[10] = "hello"; // (1)
char *str = "hello"; // (2), 원문은 char str* = "hello"; 이었는데 오타인 줄로 알고 고쳤습니다.
char str[] = "hello"; // (3)

아마 위 모두 잘 컴파일 될 것입니다. 이때 우리는 이것을 str*, str[]에 맞추어 복잡하게 생각해야 하느냐? 아닙니다. C는 그냥 모든 것이 str*일 뿐입니다. 친절하신 컴파일러께서 프로그래머가 친숙하게 받아들일 수 있도록 str[]라는 표기를 받아주시는 것 뿐이죠.

허...

위 3개의 str 정의는 조금씩 다르게 str를 정의하고 있습니다.

(1)은 char 10개짜리 배열을 정의하며, 그 원소를 각각 {'h', 'e', 'l', 'l', 'o', '\0', '\0', '\0', '\0', '\0'}으로 초기화합니다.

(2)는 char에 대한 포인터를 정의하며, 그 초기값은 static storage duration으로 "hello"를 저장하고 있으며 길이가 6인 char array의 첫 원소 주소입니다. 참고로 이 char array는 수정이 불가능합니다.

(3)은 char 6개짜리 배열을 정의하여, 그 원소를 각각 {'h', 'e', 'l', 'l', 'o', '\0'}으로 초기화합니다.

array-to-pointer decay를 고려하더라도 (2)는 뚜렷하게 구분되는 특징이 있습니다. 이 포인터가 가리키는 메모리 영역은 const가 아닌데도 불구하고 변경이 불가능하기 때문이지요. string literal에 의해 생성된 문자 배열의 값을 변경하는 건 undefined behavior입니다.

사실 그런 규칙이 있다면 애초에 string literal을 array of const char 타입으로 구성하는 게 좋았을 텐데, 여기에는 역사적인 이유(하위 호환성)가 있습니다.

반면 C++에서는 명시적으로 string literal은 const char의 배열입니다. 다만 똑같은 역사적인 이유로, C++03까지는 string literal을 non-const pointer로 받는 것이 가능했습니다. 이 문법적 허용은 C++11부터 삭제되어, C++11부터 (2)는 더 이상 유효한 코드가 아닙니다.

아무튼, 그런 이유로 C언어에서 (2)와 같이 string literal을 가리키는 포인터를 초기화하여 사용하는 건 특별한 주의가 필요합니다. 사실 그냥 C++처럼 const char *str으로 받는 게 더 좋지요.

(1)과 (3)은 거의 비슷합니다만, 말씀드렸다시피 크기가 다릅니다. 이 차이가 C언어에서 눈에 띄는 차이를 만드는 경우는 드물긴 한데(array는 대부분의 경우 pointer로 decay되면서 크기 정보를 잃습니다) 예컨대 sizeof(str)를 계산하면 그 값이 다르게 나온다는 뜻이죠. C++에서는 이 차이를 좀 더 뚜렷하게 드러낼 수 있는 경우들이 더 많습니다.

라스코니의 이미지

허..... 모르던 것을 알게 되어 너무 감사합니다.

파이썬3의 이미지

핵심을 파고드는 해설에 감사드립니다^^^

[우분투 18.04 파여폭스 나비에서 적었어요~]

Stephen Kyoungwon Kim@Google의 이미지

둘다 아닙니다.

ymir의 이미지

array name 은 non-modifiable lvalue 라서 assignment 가 안 됩니다.

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

mrx@Google의 이미지

ymir님 말씀대로
int arr[10];
int arr2[10];
arr=arr2가 안되니 자료형의 일치도 의미가 없는거 같네요
lvalue라서 할당 안된다고 보는게 제일 맞는거 같아요.

파이썬3의 이미지

혹시 이게 이해하는데 도움이 될까요... 아니라면 용서해주세요 ;;;

[우분투 18.04 파여폭스 나비에서 적어요~]

댓글 첨부 파일: 
첨부파일 크기
Image icon Screenshot from 2020-04-05 18-31-13.png217.55 KB
raymundo의 이미지

뭐 하려고 하면야 C 표준을 만들 때 다음과 같이 정할 수도 있지 않았을까요.

char str[10];  // char의 배열에 한해서
str = "hello"; // 이 대입문은
strcpy(str, "hello"); // 이것과 동일한 동작을 하도록
 
// 다른 타입의 경우도
int arr[10];
arr = { 1, 2, 3 }; // arr 의 원소를 앞에서부터 1,2,3으로 채우고 나머지를 0으로 할당

그걸 허용하지 않은 이유가 특별히 있을지 궁금해지네요. 이런 건 구글링으로도 잘 찾기 힘들어서...

좋은 하루 되세요!

Stephen Kyoungwon Kim@Google의 이미지

open question이고 trade-off가 있으며 동일한 기조를 가진 언어에서도 다르게 결정될 수 있다고 생각합니다.

https://stackoverflow.com/questions/8939090/why-array-assignment-operation-doesnt-exist-but-structure-assignment-does-in-c

저는 위의 URL에 있는 역사적 이유가 가장 그럴 듯 하다고 생각합니다.

B 언어는 array variable가 java의 reference 혹은 C의 포인터와 유사했던 것 같은데, C는 struct와 union까지 pointer variable과 function을 제외하면 모든 variable이 그 object 자체를 나타내는 것 같습니다. (e.g. int x; 에서 x와 int A[10];에서의 A가 유사합니다. java의 int[] r =new int[10];에서 r의 의미는 그 둘과 좀 다르구요)

이걸 전제로 할 때, array의 assignment를 허용하려면 말씀하신 대로, reference copy가 아닌, str(n)cpy를 해야 겠지요. struct는 그렇게 하고 있구요.

만약 그렇게 했다면, 역사적으로 B로 쓰여진 코드에서 array variable assignment 부분이 그대로 컴파일 될 텐데 의미는 달라져서 (전자는 Java와 같고 후자는 strncpy) 잡아내기 어려운 미묘한 에러가 발생할 수 있었다는 것으로 보이네요. 그래서 차라리 컴파일 에러를 내고 그때마다 고칠 수 있게끔, struct와는 달리, array의 assignment는 허용하지 않은 것 같습니다.

시간이 많이 흘렀는데도 여전히 그 부분이 변화되지 않은 데에는 좀더 다른 이유가 있을 것 같습니다. 아마 편익 대비 비용이 크지 않다고 그간의 standard에서 생각된 것 같습니다.

비용은 아마 LHS와 RHS 모두, 혹은 후자의 사이즈를 모르는 경우가 있다는 데서 생길 것 같습니다. LHS의 사이즈를 아는 경우에만 허용하고 LHS의 사이즈에만 근거해서 카피를 하는 식으로 정의한다면, RHS 사이즈 때문에 런타임 에러가 발생할 수 있을 것 같습니다. C에서 흔히 그러듯 프로그래머의 책임으로 맡길 수도 있겠습니다만, 이렇게 해서 프로그래머가 얻는 편의성이 그만한 값어치를 할 만큼 크지 않다고 생각하지 않았을까요?

raymundo의 이미지

오오 역시 말 꺼내보길 잘했네요. 이런 이야기는 정말 흥미롭습니다. 감사합니다.

좋은 하루 되세요!

jick의 이미지

아시겠지만 C는 굉장히 옛날에 만들어졌습니다. 위키피디아에 따르면 1972년도에 개발되었으며 PDP-11이라는 머신에서 처음 돌아갔다고 하는데요, 이 기계는 대략 이렇게 생겼습니다:

https://en.wikipedia.org/wiki/PDP-11

이 링크에 따르면 초기 모델은 사용할 수 있는 최대 메모리가 64KB였다고 하네요.

https://gunkies.org/wiki/PDP-11/20

> This bus had an addressing range of 18 bits, but only 16 were used, which led to the 32 Kword (as mentioned, one word was 16 bits, so this is equal 64 Kbyte) memory limit.

참고로 지금 보고 계시는 이 페이지의 HTML이 50KB가 넘습니다. 그러니까 이 페이지를 다운로드해서 텍스트 파일에 저장하면 그것만으로 메모리가 꽉 차는 머신에서 C를 컴파일하고 실행시켜야 하는 겁니다. 배열을 만나면 알아서 복사하는 것 같은 건 지금 생각하면 기본이지만 당시 기준으로 보면 "아니 그런 쓸데 없는 기능을 넣느라 낭비할 메모리가 어디 있어, 그냥 프로그래머가 for 루프 쓰면 되잖아?"가 되는 거죠.

...뭐 그 이후는 다들 그렇게 쓰기 시작했기 때문에 더 이상 아무도 바꿀 필요성을 느끼지 못했다...라는 얘기일지도... -_-;;

Stephen Kyoungwon Kim@Google의 이미지

char str[10] = {0};
str = "hello";

str도 "hello"도 각각 이미 메모리에 버퍼는 잡아놓은 상태죠. 이 카피 과정을 loop으로 하느냐, 저 statement를 허용해 주느냐를 비교한다면, 응용 프로그램이 사용하는 런타임 메모리나 코드 사이즈는 큰 차이가 없을 것 같습니다. 컴파일러의 코드 사이즈는 약간 더 커지고 수행 시간도 좀더 길어질 것 같긴 하지만, 그냥 if (array copy operator) generate_intringic_function(lhs, rhs) 이런 수준이라서 큰 차이는 없지 않았을까요?

익명 사용자의 이미지

두 배열의 크기가 같을 경우에는 대입이 문제가 없지만 다를 경우가 문제인데... 문자열 처리의 경우에는 크기가 다를 경우가 거의 백프로니까 그러한 기능이 있다 한들 큰 의미는 없을 듯 합니다.
여기에 배열의 포인터로의 암묵적인 변환규칙에 어디를 예외로 치느냐 같은 일관성 문제를 생각하면, 그리 간단하게 도입할 문제가 아닙니다.

Stephen Kyoungwon Kim@Google의 이미지

두세칸 위의 제 다른 댓글에도 비슷한 얘기가 있습니다.

LHS의 사이즈를 알아야 가능한 얘기인데, 이건 AST 상에서 타입이 T [N] 꼴로 남아 있어야 가능합니다. 예컨대 function parameter로 넘어오는 경우엔 이미 사이즈 정보가 사라지게 되죠. 아마 기본적으로 RHS의 definition이 = operator에서 use하는 지점까지 그대로 넘어온 경우에만 가능할 거고, 대체로는 global variable이거나 같은 function의 local처럼, use의 scope block을 감싸는 scope에서 정의된 경우에는 가능하리라 생각됩니다.

RHS의 사이즈를 모르거나 설령 아는 경우에도 위험을 감수하고 프로그래머에게 맡길 수 있다고 생각합니다. 사실 C는 많은 것들을 프로그래머에게, 위험을 감수하고도 맡겨 버리죠. char* (char의 natural alignment가 1)를 void*로 캐스팅 했다가 나중에 다시 int*로 캐스팅 하는 것도 허용해 주고요. array끼리 대입하는 연산의 경우에도 그냥 말로만 RHS의 사이즈가 LHS와 같거나 작아야 한다고 말만 하는 것도 가능하다고 생각합니다.

구현을 하는 것 자체는 그리 어렵지 않다고 생각합니다. 그냥 AST에서 RHS와 LHS의 type 정보만 look up 하면 되니까요.

다만, 두세칸 위 제 댓글에서도 먼저 동의했듯이 그렇게 해서 위험을 짊어지는 대신 얻는 편익이 얼마나 되느냐가 문제인데, 그다지 크지 않다고 일반적으로 간주되어 왔던 것 같습니다.

익명 사용자의 이미지

array는 대입연산자 좌변에 올 수 없습니다. 그 이유는
1) array는 modifiable l-value가 아니다.
2) array의 암시적인 pointer address로의 변환 때문에 배열을 갖다놔도 배열이 아니게 되고 대입이 불가능해진다.
1은 사족이 아닌가 싶기도 하지만 표준에서 그렇게 말하고 있기도 하니 뭐...
둘 다 맞는 설명이 됩니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.