조건부 연산자를 사용할 때 C에서 문자열 연결을 허용하지 않는 이유는 무엇입니까?


95

다음 코드는 문제없이 컴파일됩니다.

int main() {
    printf("Hi" "Bye");
}

그러나 이것은 컴파일되지 않습니다.

int main() {
    int test = 0;
    printf("Hi" (test ? "Bye" : "Goodbye"));
}

그 이유는 무엇입니까?


95
문자열 연결은 초기 렉싱 단계의 일부입니다. 이는 C의 synatx 표현식의 일부가 아닙니다. 즉, "문자열 리터럴"유형의 값 이 없습니다 . 오히려 문자열 리터럴은 값을 형성하는 소스 코드의 어휘 요소입니다.
— Kerrek SB

24
@KerrekSB 대답을 명확히하기 위해-문자열 연결은 코드 텍스트를 컴파일 하기 전에 코드 텍스트를 전처리하는 부분입니다 . 삼항 연산자는 런타임에서 평가되지만 코드가 컴파일 된 후 (또는 모든 것이 상수 인 경우 컴파일 타임에 수행 될 수 있습니다).
— 유진 Sh.

2
세부 사항 :이 게시물에 "Hi"와 "Bye"있는 문자열 리터럴 이 아닌 문자열 은 C 표준 라이브러리에서 사용 된 바와 같이. 로 문자열 리터럴 , 컴파일러는 연결할 것입니다 "H\0i" "B\0ye". sprintf(buf,"%s%s", "H\0i" "B\0ye");
— 똑같지

15
당신이 할 수없는 동일한 이유a (some_condition ? + : - ) b
— user253751

4
도 있습니다 printf("Hi" ("Bye"));, 그것은 삼항 연산자를 필요로하지 않습니다 - 작동하지 않습니다 괄호만으로도 충분합니다 ( printf("Hi" test ? "Bye" : "Goodbye")컴파일되지는 않지만). 문자열 리터럴 뒤에 올 수있는 토큰의 수는 제한되어 있습니다. 쉼표 ,, 여는 대괄호 [, 닫는 대괄호 ]( 1["abc"]— 및 예, 끔찍함), 닫는 대괄호 ), 닫는 중괄호 }(이니셜 라이저 또는 유사한 컨텍스트에서) 및 세미콜론 ;은 합법적입니다 (및 다른 문자열 리터럴). 다른 사람이 있는지 잘 모르겠습니다.
— Jonathan Leffler

답변:


121

C 표준 (5.1.1.2 번역 단계)에 따름

1 번역의 구문 규칙 중 우선 순위는 다음 단계로 지정됩니다 .6)

  1. 인접한 문자열 리터럴 토큰이 연결됩니다.

그리고 그 후에야

  1. 토큰을 구분하는 공백 문자는 더 이상 중요하지 않습니다. 각 전처리 토큰은 토큰으로 변환됩니다. 결과 토큰은 구문 및 의미 론적으로 분석되고 번역 단위로 번역됩니다 .

이 건설에서

"Hi" (test ? "Bye" : "Goodbye")

인접한 문자열 리터럴 토큰이 없습니다. 따라서이 구성은 유효하지 않습니다.


43
이것은 C에서 허용되지 않는다는 주장을 반복 할뿐입니다 . 질문 인 이유를 설명하지 않습니다 . 왜 5 시간 동안 26 개의 업보를 축적했는지 모르겠습니다. 축하합니다.
— 궤도의 가벼운 경주

4
여기에서 @LightnessRacesinOrbit에 동의해야합니다. 왜 안 (test ? "Bye" : "Goodbye")문자열 리터럴 중 하나를 평가 해 본질적으로 만들기 "Hi" "Bye" 나 "Hi Goodbye"? (내 질문은 다른 답변에서 답변 됨)
— Insane

48
@LightnessRacesinOrbit, 사람들이 일반적으로 C로 컴파일되지 않는 이유를 물을 때, 그들은 그것이 어떤 규칙을 위반하는지에 대한 설명을 요구하고 있으며, 왜 Standards Authors of Antiquity 가 그것을 그렇게 선택 했는지 가 아닙니다.
— user1717828

4
@LightnessRacesinOrbit 설명하는 질문은 주제에서 벗어난 것일 수 있습니다. 나는 어떤 볼 수없는 기술적 인 모든 대답은 의견을 기반으로 할 것, 그래서 사양의 저자에서 확실한 대답하지 않고, 그 구현이 가능하지 왜 이유를. 그리고 일반적으로 "실용적"또는 "대답 가능한"질문의 범주에 속하지 않습니다 ( 도움말 센터에서 요구하는대로).
— jpmc26

12
@LightnessRacesinOrbit "C 표준이 그렇게 말했기 때문에" 이유를 설명합니다 . 이 규칙이 정의 된대로 정의 된 이유에 대한 질문은 주제를 벗어납니다.
— user11153

135

C11 표준, §5.1.1.2 장에 따라 인접한 문자열 리터럴 연결 :

인접한 문자열 리터럴 토큰이 연결됩니다.

번역 단계 에서 발생합니다 . 반면에 :

printf("Hi" (test ? "Bye" : "Goodbye"));

런타임에 평가되는 조건부 연산자가 포함됩니다 . 따라서 컴파일 타임에 번역 단계 중에 인접한 문자열 리터럴이 없으므로 연결이 불가능합니다. 구문이 잘못되어 컴파일러에서보고합니다.


이유 부분 에 대해 좀 더 자세히 설명하기 위해 전처리 단계 동안 인접한 문자열 리터럴이 연결되어 단일 문자열 리터럴 (토큰)로 표시됩니다. 그에 따라 스토리지가 할당되고 연결된 문자열 리터럴은 단일 엔티티 (하나의 문자열 리터럴) 로 간주됩니다 .

반면에 런타임 연결의 경우 대상에는 연결된 문자열 리터럴 을 보유하기에 충분한 메모리가 있어야합니다. 그렇지 않으면 예상되는 연결된 출력에 액세스 할 수있는 방법이 없습니다 . 지금의 경우 문자열 리터럴 , 그들은 이미 컴파일 시간에 메모리를 할당 할 수 없습니다 연장 더 이상 들어오는 입력에 맞게 으로 또는 추가 원본 콘텐츠에 . 즉, 연결된 결과를 단일 문자열 리터럴 로 액세스 (표시) 할 수있는 방법이 없습니다 . 따라서이 구조는 본질적으로 잘못되었습니다.

참고로 런타임 문자열 ( 리터럴 아님) 연결의 경우 strcat()두 문자열 을 연결 하는 라이브러리 함수 가 있습니다 . 설명은 다음을 언급합니다.

char *strcat(char * restrict s1,const char * restrict s2);

이 strcat()함수는에서 가리키는 문자열 s2의 끝에 (종료 널 문자 포함)가 가리키는 문자열의 사본을에서 가리키는 문자열 의 끝에 추가 합니다s1 . 의 초기 문자 s2는 끝에있는 null 문자를 덮어 씁니다 s1. [...]

그래서 우리는 이 문자열 리터럴s1 이 아니라 문자열 임을 알 수 있습니다 . 그러나의 내용은 어떤 식 으로든 변경되지 않으므로 문자열 리터럴 일 수 있습니다.s2


1
다음에 대한 추가 설명을 추가하고 싶을 수 있습니다 strcat. 대상 배열은 문자를받을 수있을만큼 길어야하며 s2이미 존재하는 문자 뒤에 null 종결자가 있어야합니다.
— chqrlie

39

문자열 리터럴 연결은 컴파일 타임에 전처리기에 의해 수행됩니다. 이 연결이 다음의 값을 인식 할 방법이 없습니다.test 은 프로그램이 실제로 실행될 때까지 알려지지 않은 이 없습니다. 따라서 이러한 문자열 리터럴은 연결할 수 없습니다.

일반적인 경우는 컴파일 타임에 알려진 값에 대해 이와 같은 구성이 없기 때문에 C 표준은 자동 연결 기능을 가장 기본적인 경우로 제한하도록 설계되었습니다. .

그러나이 제한을 그런 식으로 표현하지 않았거나 제한이 다르게 구성되었다하더라도 연결을 런타임 프로세스로 만들지 않고서는 예제를 실현할 수 없습니다. 그리고이를 위해 strcat.


3
나는 단지 가정을 읽었습니다. 당신이 말하는 것은 거의 유효하지만 소스가 없기 때문에 소스를 제공 할 수 없습니다. C와 관련된 유일한 출처는 표준 문서입니다. (많은 경우에 모호하지만) 어떤 것들이 왜 그런지 설명하지 않고 특정 방식 이어야만한다고 말합니다. 그래서 모스크바의 대답에서 블라드에 대해 그토록 까다로운 행동은 부적절합니다. OP는 "왜 그런 식입니까?"로 나눌 수 있기 때문에 -유일한 정답은 "C이기 때문에 C가 정의 된 방식이기 때문에"라는 유일한 정답입니다.
— dhein

1
이것은 설명이 부족합니다. 그러나 여기서 다시 beeing은 Vlad의 대답이 핵심 문제에 대한 설명으로 훨씬 더 많은 것을 제공한다고 말했습니다. 다시 한 번 말 : 내가 확인할 수있는 정보는 관련이 있고 정확하지만 귀하의 불만에 동의하지 않습니다. 그리고 나는 당신이 주제를 벗어났다고 생각하지는 않지만, 내 POV에서 더 주제를 벗어난 것입니다.
— dhein

11
@Zaibis : 출처는 나입니다. Vlad의 대답은 전혀 설명이 아닙니다. 그것은 단지 질문의 전제에 대한 확인 일뿐입니다. 확실히 그들 중 어느 것도 "주제에서 벗어난 것"이 아닙니다 (이 용어가 의미하는 바를 찾아 볼 수 있습니다). 그러나 당신은 당신의 의견에 대한 권리가 있습니다.
— 궤도의 가벼운 경주

위의 의견을 읽은 후에도 OP 가이 답변에 대한 추가 설명을 요청하지 않는 한이 답변이 완벽한 답변이라고 믿습니다.이 답변을 누가 반대했는지 여전히 궁금합니다. ᶘ ᵒᴥᵒᶅ
— Mohit Jain

2
나는 왜이 대답이 당신에게 받아 들여지고 @VladfromMoscow는 그렇지 않은지 구별 할 수 없습니다. 둘 다 같은 말을하고 그의 인용이 인용으로 뒷받침되고 당신은 그렇지 않습니다.
— Marquis of Lorne

30

C에는 string유형 이 없기 때문 입니다. 문자열 리터럴은 포인터로 char참조되는 배열 로 컴파일됩니다 char*.

C는 첫 번째 예제에서와 같이 인접 리터럴 을 컴파일 타임에 결합 할 수 있도록 합니다. C 컴파일러 자체에는 문자열에 대한 지식이 있습니다. 그러나이 정보는 런타임에 존재하지 않으므로 연결이 발생할 수 없습니다.

컴파일 과정에서 첫 번째 예제는 다음과 같이 "번역"됩니다.

int main() {
    static const char char_ptr_1[] = {'H', 'i', 'B', 'y', 'e', '\0'};
    printf(char_ptr_1);
}

프로그램이 실행되기 전에 컴파일러에서 두 문자열을 단일 정적 배열로 결합하는 방법에 유의하십시오.

그러나 두 번째 예는 다음과 같이 "번역"됩니다.

int main() {
    static const char char_ptr_1[] = {'H', 'i', '\0'};
    static const char char_ptr_2[] = {'B', 'y', 'e', '\0'};
    static const char char_ptr_3[] = {'G', 'o', 'o', 'd', 'b', 'y', 'e', '\0'};
    int test = 0;
    printf(char_ptr_1 (test ? char_ptr_2 : char_ptr_3));
}

이것이 컴파일되지 않는 이유가 명확해야합니다. 삼항 연산자 ?는 "문자열"이 더 이상 존재하지 않고 포인터에 char의해 참조되는 단순한 배열 로만 존재하는 컴파일 타임이 아닌 런타임에 평가됩니다 char*. 인접한 달리 문자열 리터럴 , 인접한 문자 포인터는 단순히 구문 오류가 있습니다.


2
훌륭한 대답, 아마도 여기에서 가장 좋습니다. "왜 이것이 컴파일되지 않는지 명확해야합니다." "삼항 연산자는 컴파일 시간이 아닌 런타임에 평가 된 조건이기 때문에"로 확장하는 것을 고려할 수 있습니다 .
— cat

나머지 포인터에 대해서도 비슷 하지 static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};않아야 static const char *char_ptr_1 = "HiBye";합니까?
— Spikatrix

@CoolGuy static const char *char_ptr_1 = "HiBye";컴파일러 를 작성할 때 라인을로 변환 static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};하므로 "문자열처럼"작성해서는 안됩니다. 답변에서 말했듯이 문자열은 문자 배열로 컴파일되며 가장 "원시"형식의 문자 배열을 할당하는 경우 쉼표로 구분 된 문자 목록을 사용합니다. 다음과 같이됩니다.static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};
— Ankush

3
@Ankush 예. 그러나 비록 static const char str[] = {'t', 'e', 's', 't', '\0'};과 동일 static const char str[] = "test";, static const char* ptr = "test";있다 없다 와 같은 static const char* ptr = {'t', 'e', 's', 't', '\0'};. 전자는 유효하고 컴파일되지만 후자는 유효하지 않으며 예상대로 수행합니다.
— Spikatrix

마지막 단락을 구체화하고 코드 예제를 수정했습니다. 감사합니다!
— Unsigned

13

두 분기 모두 런타임에 선택할 컴파일 시간 문자열 상수를 생성하도록하려면 매크로가 필요합니다.

#include <stdio.h>
#define ccat(s, t, a, b) ((t)?(s a):(s b))

int
main ( int argc, char **argv){
  printf("%s\n", ccat("hello ", argc > 2 , "y'all", "you"));
  return 0;
}

10

그 이유는 무엇입니까?

삼항 연산자를 사용하는 코드는 조건부로 두 문자열 리터럴 중에서 선택합니다. 알려 지거나 알려지지 않은 조건에 관계없이 컴파일 타임에 평가할 수 없으므로 컴파일 할 수 없습니다. 이 문장조차 printf("Hi" (1 ? "Bye" : "Goodbye"));컴파일되지 않습니다. 그 이유는 위의 답변에 자세히 설명되어 있습니다. 의 또 다른 가능성 컴파일 할 삼항 연산자가 유효 사용하여 이러한 문을은 , 또한 포함 할 형식의 태그 와 같은 포맷 된 삼항 연산자 문의 결과 추가 인수 로를 printf. 그럼에도 불구하고, printf()출력물은 이러한 문자열을 런타임시 에만 "연결된"느낌을줍니다 .

#include <stdio.h>

int main() {
    int test = 0;
    printf("Hi %s\n", (test ? "Bye" : "Goodbye")); //specify format and print as result
}

3
그래서 튜토리얼 사이트가 아닙니다. 튜토리얼이 아닌 OP에 대한 답변을 제공해야합니다.
— Michi

1
이것은 OP의 질문에 대답하지 않습니다. OP의 근본적인 문제를 해결하려는 시도 일 수 있지만 이것이 무엇인지 실제로는 모릅니다.
— Keith Thompson

1
printf형식 지정자가 필요 하지 않습니다 . 컴파일 타임에 연결 만 수행 된 경우 (그렇지 않은 경우) OP의 printf 사용이 유효합니다.
— David Conrad

귀하의 의견에 감사드립니다, @David Conrad. 내 엉성한 문구는 마치 진술 printf()에 형식 태그가 필요한 것처럼 보이게 할 것입니다. 이것은 절대적으로 사실이 아닙니다. 수정되었습니다!
— user3078414

더 나은 표현입니다. +1 감사합니다.
— David Conrad

7

에 printf("Hi" "Bye"); 당신은 컴파일러가 하나의 배열로 만들 수있는 문자의 두 개의 연속적인 배열을 가지고있다.

에서는 printf("Hi" (test ? "Bye" : "Goodbye"));당신이 가진 하나 개의 배열 (배열의 첫 번째 요소에 대한 포인터로 변환) 문자 포인터 하였다. 컴파일러 는 배열과 포인터를 병합 할 수 없습니다 .


0

질문에 답하기 위해 printf의 정의로 이동합니다. printf 함수는 const char * 를 인수로 예상 합니다. "Hi"와 같은 문자열 리터럴은 const char *입니다. 그러나 다음 (test)? "str1" : "str2"과 같은 표현식은 const char *가 아닙니다. 왜냐하면 그러한 표현식의 결과는 런타임에서만 발견되고 따라서 컴파일 시간에 결정되지 않기 때문에 컴파일러가 불평하게 만드는 사실입니다. 반면에 이것은 완벽하게 잘 작동합니다.printf("hi %s", test? "yes":"no")


* 그러나 같은 표현 (test)? "str1" : "str2"은 NOT a const char*... 물론 그렇습니다! 상수 표현식은 아니지만 유형 은 const char * 입니다. 작성하는 것이 완벽 할 것 printf(test ? "hi " "yes" : "hi " "no")입니다. 영업 이익의 문제와는 아무 상관이없는 printf, "Hi" (test ? "Bye" : "Goodbye")아무리 표현의 맥락이 무엇인지 구문 오류입니다를.
— chqrlie

동의합니다. 식의 출력을 식 자체와 혼동했습니다
— Stats_Lover

-4

printf 함수의 매개 변수 목록이 다음과 같으므로 컴파일되지 않습니다.

(const char *format, ...)

과

("Hi" (test ? "Bye" : "Goodbye"))

매개 변수 목록에 맞지 않습니다.

gcc는 다음과 같이 상상하여 이해하려고합니다.

(test ? "Bye" : "Goodbye")

매개 변수 목록이고 "Hi"가 함수가 아니라고 불평합니다.


6
Stack Overflow에 오신 것을 환영합니다. printf()인수 목록 과 일치하지 않는다는 것이 맞습니다 . 그러나 표현식이 printf()인수 목록 에서만 유효하지 않기 때문 입니다. 즉, 문제에 대해 너무 전문적인 이유를 선택했습니다. 일반적인 문제는 "Hi" (C에서 유효하지 않다는 것입니다 printf(). 이 답변을 삭제하기 전에이 답변을 삭제하는 것이 좋습니다.
— Jonathan Leffler

그것은 C가 작동하는 방식이 아닙니다. 이것은 PHP와 같은 문자열 리터럴을 호출하려는 것으로 구문 분석되지 않습니다.
— cat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.