x86이 못생긴 이유는 무엇입니까? 다른 사람과 묶음에서 왜 열등한 것입니까? [닫은]
최근에 나는 몇 가지 SO 아카이브를 발견 x86 아키텍처에 대한 자료를 발견했습니다.
서버 및 미니 / 메인 프레임 및 혼합 코어에 대해 서로 다른 CPU 아키텍처가 필요한 이유는 무엇입니까? 말한다
" PC 아키텍처는 엉망이, 어떤 OS 개발자가 말할 것입니다. "어셈블리 언어를 배우는 것이 그만한 가치가 있습니까?( 아카이브 됨 )
" x86 아키텍처가 기껏해야 끔찍한 사실을 깨달으십시오 "x86 어셈블러를 배우는 쉬운 방법이 있습니까? 말한다
" 은 이해하기가 훨씬 간단하기 때문에 대부분의 대학은 MIPS 같은 조립 조립, 86 어셈블리 정말 밉다 "
그리고 같은 더 많은 댓글
검색을 시도했지만 이유를 제안했습니다. 내가 익숙한 아키텍처이기 때문에 x86이 나쁘다고 생각하지 않습니다.
누군가가 x86을 다른 사람에 비해 못생긴 / 나쁨 / 열등 등 생각하는 이유를 친절하게 줄 수있는 줄 수 있습니까?
이에 대한 몇 가지 가능한 이유 :
- x86은 오래된 ISA입니다 (원조는 결국 8086).
- x86은 여러 번 크게 발전했지만 이전 바이너리와의 하위 시스템을 유지 구매 하드웨어가 필요합니다. 예를 들어 최신 x86 하드웨어에는 기본적으로 16 비트 코드를 실행하기위한 지원이 여전히 포함되어 있습니다. 또한 리얼 모드, 보호 모드, 가상 8086 모드 및 (amd64) 롱 모드와 같은 이전 코드가 동일한 프로세서에서 상호 작용할 수있는 여러 메모리 주소 지정 모델이 있습니다. 이 일부 사람들에게 혼란 스러울 수 있습니다.
- x86은 CISC 시스템입니다. 오랫동안 MIPS 또는 ARM과 같은 RISC 시스템보다 느리다는 것을 의미했습니다. 대부분의 데이터 상호 의존성과 플래그가 있어 대부분의 급한 수준의 오류 처리 형식을 구현하기 어렵 기 때문입니다. 최신 구현에서는 x86 급을 " micro-ops " 라고하는 RISC와 승급으로 변환하여 종류의 최적화를 하드웨어에서 구현할 수 있습니다.
- 어떤면에서 x86은 열등하지 않고 단지 단지. 예를 들어 입력 / 출력은 대부분의 아키텍처에서 메모리 매핑으로 처리합니다 x86에서는 처리되지 않습니다. (주의 : 현대 x86 컴퓨터는 일반적으로 어떤 형태가 DMA의 ,하지만 지원 및 메모리 매핑을 통해 다른 하드웨어와 통신 ISA는 여전히 동일한 I / O 지침을 가지고
IN
와OUT
) - x86 ISA는 많은 방법이 매우 적기 때문에 프로그램이 다른 방법보다 자주 메모리를 반복 할 수 있습니다. 수행하는 데 필요한 추가 지침은 전달 전달 이지만 유용한 작업에 사용하는 실행 리소스를 사용합니다.지연 시간을 낮게 유지합니다. 많은 이름을 큰 물리적 인 파일로 변경하는 최신 구현은 많은 명령을 계속 사용할 수 있습니다. 32 비트 x86의 중요한 약점. x86-64의 8 개에서 16 개 및 벡터 비행의 증가는 64 비트 코드에서 32 비트보다 더 빠른 (더 빠른 호출 호출 ABI와 함께) 큰 가장 요소 중 하나이며, 너비가 증가한 것이 아닙니다. 16 개에서 32 개 정수로 추가 증가하면 도움이됩니다. (AVX512는 32 개의 벡터 영상으로 증가합니다. 부동 소수점 코드는 대기 시간이 더 길고 종종 더 많은 상수가 필요하기 때문입니다.) (주석 참조 )
x86은 많은 기능을 복잡한 아키텍처이기 때문에 x86 어셈블리 코드는 복잡합니다. 일반적인 MIPS 기계에 대한 지침 목록은 한 장의 편지 크기의 종이에 맞습니다. x86에 해당하는 목록은 더 많은 작업을 수행 할 것 목록에서 제공 할 수있는 것보다 더 큰 설명이 필요한 경우가 있습니다. 예를 들어,
MOVSB
명령어 가 수행하는 작업을 설명하려면 비교적 큰 C 코드 블록이 필요합니다.if (DF==0) *(byte*)DI++ = *(byte*)SI++; else *(byte*)DI-- = *(byte*)SI--;
이것은로드, 저장 및 두 개의 더하기 또는 빼기 (플래그 입력에 의해 제어 됨)를 수행하는 단일 한이며, 또한 RISC 시스템에서 별도의 급입니다.
MIPS (및 체계적인 방법)의 단순성이 반드시 있어야하는 것이 우월하는 것은 만드는 것이 소개 어셈블러 클래스에 대한 많은 것이 더 간단한 ISA 로 시작하는 것이 좋습니다. 일부 어셈블리 클래스는 y86 이라는 매우 단순화 된 x86 서브 세트를 사용 하는데 유용 , 실제 실제 사용에하지 않다는 점은 점 (예 : 시프트 명령 없음을 넘어서 단순화됩니다)는 기본 x86 명령 만 가르입니다.
- x86은 가변 길이 opcode를 사용하여 좀 더 구문 분석과 관련하여 하드웨어 사양을 추가합니다. 현대 시대에 CPU가 원시 계산보다 메모리가 점점 더 제한되고, 따라서 "x86 bashing"이 더 큰 시대에 비롯됩니다.
2016 Update : Anandtech는 x64 및 AArch64 아래의 opcode 크기 에 대한 토론 을 게시했습니다 .
편집 : 이것은 x86을 bash 로되 어서 는 가능성 ! 파티. 나는 선택의 여지가 거의 없었지만 질문이 표현 된 방식을 고려할 때 약간의 강타를 할 수밖에 없었다. 그러나 모든 (1)을 제외하고 수행 한 작업은 수행 한 작업 (댓글 참조). 인텔 디자이너는 어리석지 언어입니다. 아키텍쳐로 몇 가지를 달성하고 싶고, 더 많은 기능을 제공해야합니다.
내 생각에 x86에 대한 주요 노크는 CISC 기원입니다. 많은 세트에는 많은 암시 적 상호 대화가 포함되어 있습니다. 상호 보수는 칩에서 명령 재정렬과 같은 작업을 수행하기 어렵게 만듭니다. 상호 보수의 아티팩트 및 의미는 각 명령에 대해 보존하기 때문입니다.
예를 들어, 대부분의 x86에서는 정수가 더하기 및 빼기 좀 더 많고 수정합니다. 더하기 또는 빼기를 한 후 다음 작업은 종종 플래그 비트를 확인하고 오버하여 플로, 부호 비트 등을 확인하는 것입니다. 그 이후에 다른 더하기가 있으면 두 번째 더하기 실행을 시작하는 것이 안전한지 여부를 알기가 매우 어렵습니다. 첫 번째 추가의 결과가 알려지기 전에.
RISC 아키텍처에서 추가는 입력 피연산자와 오류를 지정하며 연산에 대한 모든 것입니다. 이렇게하면 모든 항목을 정렬하고 단일 파일을 실행하는 블루밍 플래그가 있기 때문에 서로 가까운 추가 작업을 훨씬 쉽게 분리 할 수 있습니다.
MIPS 스타일의 RISC 설계 인 DEC Alpha AXP 칩은 사용 가능성에서 매우 스파르타 적이 급한 세트는 급한 간 암시 적. 하드웨어 정의 스택이 없습니다. 하드웨어 정의 플래그가 없습니다. 명령 포인터조차도 OS에 정의되어 있습니다. 호출자에게 돌아가려면 호출자가 반환 할 주소를 알려주는 방법을 알아 내야했습니다. 이것은 일반적으로 OS 호출 규칙에 의해 정의되었습니다. 하지만 x86에서는 칩 하드웨어에 의해 정의됩니다.
어쨌든, 3 ~ 4 세대에 Alpha AXP 칩 설계 하드웨어에서 32 개의 int 안테나 하드웨어에서 32 개의 int 메시지와 32 개의 부동 한 전달로 구성된 스파르탄의 세트의 문자 전달로 전달 된 적 스파르탄의 세트의 문자는 80 개의 내부적으로, 실제 이름 변경, 결과적으로 의 결과가 값에 따라 나중 급한 전달되는 경우) 및 모든 종류의 거칠고 미친 성능 부스터. 그리고 모든 단소리와 휘파람으로 AXP 칩 다이는 그 정도의 펜티엄 칩 다이보다 훨씬 작으며 AXP는 훨씬 더 빨랐습니다.
x86 명령어 세트의 복잡성으로 인해 많은 종류의 실행 최적화가 불가능하지는 않더라도 엄청나게 많은 비용이 들기 때문에 x86 패밀리 트리에서 이러한 종류의 성능 급증이 크게 향상되지는 않습니다. Intel의 천재성은 더 이상 하드웨어에서 x86 명령 세트를 구현하는 것을 포기하는 것입니다. 모든 최신 x86 칩은 실제로 x86 명령을 어느 정도 해석하여 원래 x86의 모든 의미를 보존하는 내부 마이크로 코드로 변환하는 RISC 코어입니다. 그러나 마이크로 코드에 대한 RISC의 비 순차적 및 기타 최적화를 약간 허용합니다.
저는 많은 x86 어셈블러를 작성했으며 CISC 루트의 편리함을 충분히 이해할 수 있습니다. 그러나 Alpha AXP 어셈블러를 작성하는 데 시간을 할애하기 전까지는 x86이 얼마나 복잡한 지 충분히 이해하지 못했습니다. 나는 AXP의 단순성과 균일성에 매료되었습니다. 그 차이는 엄청나고 심오합니다.
x86 아키텍처는 8008 마이크로 프로세서 및 친척의 설계에서 시작되었습니다. 이 CPU는 메모리가 느린시기에 설계되었으며 CPU 다이에서 할 수 있다면 훨씬 더 빠릅니다. 그러나 CPU 다이 공간도 비쌌습니다. 이 두 가지 이유는 특별한 목적을 갖는 경향이있는 적은 수의 레지스터와 모든 종류의 문제와 제한이있는 복잡한 명령어 세트가있는 이유입니다.
같은 시대의 다른 프로세서 (예 : 6502 제품군)도 비슷한 제한과 단점이 있습니다. 흥미롭게도 8008 시리즈와 6502 시리즈는 모두 임베디드 컨트롤러로 설계되었습니다. 그 당시에도 임베디드 컨트롤러는 어셈블러로 프로그래밍 될 것으로 예상되었으며 여러면에서 컴파일러 작성자가 아닌 어셈블리 프로그래머에게 적합했습니다. (컴파일러 작성을 수용 할 때 어떤 일이 발생하는지 VAX 칩을보십시오.) 설계자는 이들이 범용 컴퓨팅 플랫폼이 될 것이라고 기대하지 않았습니다. 이것이 바로 POWER 아키텍처의 전임자와 같은 것입니다. 물론 가정용 컴퓨터 혁명은 그것을 바꾸어 놓았습니다.
여기에 몇 가지 추가 측면이 있습니다.
작업 "a = b / c"x86이이를 다음과 같이 구현한다고 생각해보십시오.
mov eax,b
xor edx,edx
div dword ptr c
mov a,eax
div 명령의 추가 보너스로 edx에는 나머지가 포함됩니다.
RISC 프로세서는 먼저 b와 c의 주소를로드하고, b와 c를 메모리에서 레지스터로로드하고, 분할을 수행하고 a의 주소를로드 한 다음 결과를 저장해야합니다. Dst, src 구문 :
mov r5,addr b
mov r5,[r5]
mov r6,addr c
mov r6,[r6]
div r7,r5,r6
mov r5,addr a
mov [r5],r7
여기에는 일반적으로 나머지가 없습니다.
변수가 포인터를 통해로드되는 경우 두 시퀀스 모두 더 길어질 수 있지만 다른 레지스터에 이미로드 된 하나 이상의 포인터가있을 수 있기 때문에 RISC에 대한 가능성은 적습니다. x86은 레지스터가 적기 때문에 포인터가 그 중 하나에있을 가능성이 더 적습니다.
장점과 단점:
RISC 명령어는 명령어 스케줄링을 개선하기 위해 주변 코드와 혼합 될 수 있습니다. x86에서는 대신 CPU 자체 내에서이 작업을 수행하는 (순서에 따라 다소 잘) 가능성이 적습니다. 위의 RISC 시퀀스는 일반적으로 32 비트 아키텍처에서 28 바이트 길이 (각각 32 비트 / 4 바이트 너비의 명령어 7 개)입니다. 이렇게하면 명령어를 가져올 때 (7 회 가져 오기) 오프 칩 메모리가 더 많이 작동합니다. 밀도가 높은 x86 시퀀스에는 더 적은 명령어가 포함되어 있으며 너비는 다양하지만 평균 4 바이트 / 명령어도 여기에서 볼 수 있습니다. 7 번의 가져 오기 속도를 높이기위한 명령어 캐시가 있어도 x86과 비교하여 다른 곳에서 3 개의 부족한 부분이 있음을 의미합니다.
저장 / 복원 할 레지스터가 더 적은 x86 아키텍처는 아마도 스레드 전환을 수행하고 RISC보다 빠르게 인터럽트를 처리 할 것임을 의미합니다. 저장 및 복원 할 레지스터가 많을수록 인터럽트를 수행하는 데 더 많은 임시 RAM 스택 공간이 필요하고 스레드 상태를 저장하기위한 더 영구적 인 스택 공간이 필요합니다. 이러한 측면은 x86을 순수한 RTOS 실행에 더 적합한 후보로 만들 것입니다.
좀 더 개인적인 메모에서 x86보다 RISC 어셈블리를 작성하는 것이 더 어렵다는 것을 알았습니다. RISC 루틴을 C로 작성하고 생성 된 코드를 컴파일하고 수정하여이 문제를 해결합니다. 이것은 코드 생산 관점에서 더 효율적이고 실행 관점에서는 덜 효율적입니다. 추적해야 할 모든 32 개의 레지스터. x86에서는 그 반대입니다. "실제"이름을 가진 6-8 개의 레지스터를 사용하면 문제를보다 쉽게 관리 할 수 있고 생성 된 코드가 예상대로 작동 할 것이라는 확신을 갖게됩니다.
추한? 그것은 보는 사람의 눈에 있습니다. 나는 "다른"을 선호합니다.
이 질문에는 잘못된 가정이 있다고 생각합니다. x86을 추악하다고 부르는 것은 주로 RISC에 집착하는 학자들입니다. 실제로 x86 ISA는 RISC ISA에서 5-6 개의 명령어를 사용하는 단일 명령어 작업으로 수행 할 수 있습니다. RISC 팬은 최신 x86 CPU가 이러한 "복잡한"명령을 마이크로 옵스로 분해한다고 반박 할 수 있습니다. 하나:
- 많은 경우에 그것은 부분적으로 만 사실이거나 전혀 사실이 아닙니다. x86에서 가장 유용한 "복잡한"명령어는
mov %eax, 0x1c(%esp,%edi,4)
주소 지정 모드 와 같은 것 입니다. - 현대 기계에서 종종 더 중요한 것은 소비 된주기의 수 (대부분의 작업이 CPU 바인딩이 아니기 때문에)가 아니라 코드의 명령 캐시 영향입니다. 5-6 개의 고정 크기 (일반적으로 32 비트) 명령어는 5 바이트를 넘지 않는 복잡한 명령어 하나 이상에 캐시에 영향을줍니다.
x86은 약 10 ~ 15 년 전에 RISC의 모든 좋은 측면을 실제로 흡수했으며 RISC의 나머지 특성 (실제로 정의하는 요소 -최소 명령 집합)은 해롭고 바람직하지 않습니다.
CPU 제조 비용과 복잡성과 에너지 요구 사항 외에도 x86은 최고의 ISA 입니다. 달리 말하는 사람은 이데올로기 나 의제를 추론하는 데 방해가됩니다.
반면에 CPU 비용이 중요한 임베디드 장치 또는 에너지 소비가 가장 중요한 임베디드 / 모바일 장치를 대상으로하는 경우 ARM 또는 MIPS가 더 적합 할 것입니다. 쉽게 3 ~ 4 배 더 큰 코드를 처리하는 데 필요한 추가 램 및 바이너리 크기를 처리해야하며 성능에 근접 할 수는 없습니다. 이것이 중요한지는 당신이 무엇을 실행할 것인지에 달려 있습니다.
x86 어셈블러 언어는 그렇게 나쁘지 않습니다. 기계 코드에 도달했을 때 정말 추악 해지기 시작합니다. 명령어 인코딩, 주소 지정 모드 등은 대부분의 RISC CPU보다 훨씬 더 복잡합니다. 또한 이전 버전과의 호환성을 위해 내장 된 추가 재미가 있습니다. 프로세서가 특정 상태에있을 때만 시작되는 기능입니다.
예를 들어, 16 비트 모드에서 주소 지정은 완전히 이상하게 보일 수 있습니다. 에 대한 주소 지정 모드가 [BX+SI]
있지만 [AX+BX]
. 필요에 따라 사용할 수있는 레지스터에 값이 있는지 확인해야하기 때문에 이와 같은 것은 레지스터 사용을 복잡하게 만드는 경향이 있습니다.
(다행히도 32 비트 모드는 훨씬 더 건전하며 (예를 들어 세분화와 같이 때때로 다소 이상하지만), 16 비트 x86 코드는 부트 로더 및 일부 임베디드 환경 외부에서는 더 이상 관련이 없습니다.)
인텔이 x86을 궁극의 프로세서로 만들려고했던 옛날부터 남은 것도 있습니다. 아무도 실제로 더 이상 수행하지 않는 작업을 수행하는 몇 바이트 길이의 명령은 솔직히 너무 느리거나 복잡하기 때문입니다. 두 가지 예에 대한 ENTER 및 LOOP 명령어 -C 스택 프레임 코드는 대부분의 컴파일러에서 "enter"가 아니라 "push ebp; mov ebp, esp"와 같습니다.
나는 전문가는 아니지만 사람들이 좋아하지 않는 많은 기능이 잘 수행되는 이유 일 수 있습니다. 몇 년 전에는 레지스터 (스택 대신), 레지스터 프레임 등이 아키텍처를 인간에게 더 단순하게 보이게하는 좋은 솔루션으로 간주되었습니다. 그러나 요즘 중요한 것은 캐시 성능이며 x86의 가변 길이 단어를 사용하면 캐시에 더 많은 명령을 저장할 수 있습니다. 반대자들이 한때 칩의 절반을 차지했다고 지적했던 "명령 디코딩"은 더 이상 그렇게 많이되지 않습니다.
저는 병렬 처리가 오늘날 가장 중요한 요소 중 하나라고 생각합니다. 적어도 이미 사용할 수있을만큼 충분히 빠르게 실행되는 알고리즘의 경우입니다. 소프트웨어에서 높은 병렬 처리를 표현하면 하드웨어가 메모리 대기 시간을 분할 (또는 완전히 숨길 수 있음) 할 수 있습니다. 물론 더 먼 미래의 아키텍처는 아마도 양자 컴퓨팅과 같은 것입니다.
nVidia로부터 Intel의 실수 중 하나는 바이너리 형식을 하드웨어에 가깝게 유지했다는 것입니다. CUDA의 PTX는 빠른 레지스터 사용 계산 (그래프 채색)을 수행하므로 nVidia는 스택 머신 대신 레지스터 머신을 사용할 수 있지만 여전히 모든 오래된 소프트웨어를 손상시키지 않는 업그레이드 경로를 가지고 있습니다.
x86을 대상으로하는 컴파일러를 작성하려고 시도하거나 x86 머신 에뮬레이터를 작성하거나 하드웨어 설계에서 ISA를 구현하려고해도 답의 일부를 얻을 수있을 것이라고 생각합니다.
나는 "x86이 추하다!"라는 것을 이해하지만 MIPS (예를 들어)보다 x86 어셈블리를 작성 하는 것이 더 재미 있다고 생각합니다 . 후자는 지루합니다. 그것은 항상 인간보다는 컴파일러에게 좋을 것입니다. 나는 칩이 시도한다면 컴파일러 작성자에게 더 적대적 일 수 있다고 확신하지 않습니다.
저에게 가장 추악한 부분은 (실제 모드) 분할이 작동하는 방식입니다. 모든 물리적 주소에는 4096 개의 segment : offset 별칭이 있습니다. 언제 마지막으로 필요 했습니까? 세그먼트 부분이 32 비트 주소의 고차 비트라면 훨씬 더 간단했을 것입니다.
사람들이 이미 언급 한 이유 외에도 :
- x86-16은 하나의 메모리 위치를 최대 4096 개의 다른 방식으로 주소 지정하고 RAM을 1MB로 제한하고 프로그래머가 두 가지 다른 크기의 포인터를 처리하도록 하는 다소 이상한 메모리 주소 지정 체계 를 가졌 습니다. 다행히도 32 비트로의 이동으로 인해이 기능이 불필요 해졌지만 x86 칩은 여전히 세그먼트 레지스터를 가지고 있습니다.
- 동안하지 않는 오류 86 자체 , 86 호출 규칙은 (MS-DOS는 어떤 컴파일러와 함께 제공되지 않은 주로하기 때문에)의 혼란으로 우리를 떠나 있었다 MIPS와 같은 표준화되지 않은
__cdecl
,__stdcall
,__fastcall
, 등
x86에는 매우 제한된 범용 레지스터 세트가 있습니다.
효율적인로드 / 스토어 방법론 대신 가장 낮은 수준 (CISC 지옥)에서 매우 비효율적 인 개발 스타일을 장려합니다.
인텔은 어리석은 세그먼트 / 오프셋-메모리 주소 지정 모델을 도입하여 (현재 이미!) 오래된 기술과 호환되도록 끔찍한 결정을 내 렸습니다.
모두가 32 비트를 사용하던 당시 x86은 빈약 한 16 비트 (대부분-8088-8 비트 외부 데이터 경로만으로도 더 무섭습니다!) CPU로 주류 PC 세계를 막았습니다.
저에게 (그리고 저는 개발자의 관점에서 모든 세대의 PC를 본 DOS 베테랑입니다!) 포인트 3은 최악이었습니다.
90 년대 초 (주류!)에 있었던 다음과 같은 상황을 상상해보십시오.
a) 레거시 이유로 미친 제한이있는 운영 체제 (쉽게 액세스 할 수있는 640kB의 RAM)-DOS
b) RAM 측면에서 더 많은 작업을 수행 할 수있는 운영 체제 확장 (Windows)이지만 게임 등의 경우에는 제한적이며 지구상에서 가장 안정적인 것은 아닙니다. 여기서 90 년대 초반에 대해 이야기하고 있습니다)
c) 대부분의 소프트웨어는 여전히 DOS였으며 일부 프로그램은 좋아하고 다른 프로그램은 싫어하는 EMM386.exe가 있었기 때문에 특수 소프트웨어 용으로 부팅 디스크를 자주 만들어야했습니다 (특히 게이머-당시에는 AVID 게이머였습니다-내가 무엇을 알고 있는지 여기에 대해 이야기하고 있습니다)
d) 우리는 MCGA 320x200x8 비트로 제한되었습니다 (좋아요, 특별한 트릭으로 조금 더 있었고 360x480x8은 가능했지만 런타임 라이브러리 지원없이 만 가능했습니다), 다른 모든 것은 지저분하고 끔찍했습니다 ( "VESA"-lol).
e) 그러나 하드웨어 측면에서 우리는 최대 1024x768을 지원하는 몇 메가 바이트의 RAM과 VGA 카드가있는 32 비트 시스템을 사용했습니다.
이 나쁜 상황의 이유는 무엇입니까?
인텔의 단순한 디자인 결정. 기계 명령어 레벨 (바이너리 레벨이 아님!)이 이미 죽어가는 것에 대한 호환성은 8085라고 생각합니다. 다른 겉보기에 관련되지 않은 문제 (그래픽 모드 등)는 기술적 인 이유와 매우 좁기 때문에 관련되었습니다. x86 플랫폼이 그 자체로 가져온 마음이있는 아키텍처.
오늘날 상황은 다르지만 어셈블러 개발자 나 x86 용 컴파일러 백엔드를 빌드하는 사람에게 문의하십시오. 엄청나게 적은 수의 범용 레지스터는 끔찍한 성능 킬러 일뿐입니다.
'IT' 카테고리의 다른 글
오류 : 삭제 된 기능 사용 (0) | 2020.08.19 |
---|---|
Asyncio.gather 대 asyncio.wait (0) | 2020.08.19 |
“BEGIN_OBJECT가 필요하지만 1 행 1 열에서 STRING이었습니다.” (0) | 2020.08.19 |
jQuery 및 TinyMCE : 텍스트 영역 값이 적용되지 않음 (0) | 2020.08.18 |
이미지가 더 작아도 UITableViewCell의 ImageView를 고정 크기로 만드는 방법 (0) | 2020.08.18 |