พอยน์เตอร์ใน C: ควรใช้เครื่องหมายและเครื่องหมายดอกจันเมื่อใด


298

ฉันเพิ่งเริ่มต้นด้วยตัวชี้และฉันสับสนเล็กน้อย ฉันรู้ว่า&หมายถึงที่อยู่ของตัวแปรและ*สามารถใช้ด้านหน้าของตัวแปรตัวชี้เพื่อรับค่าของวัตถุที่ถูกชี้โดยตัวชี้ แต่สิ่งต่าง ๆ จะทำงานแตกต่างกันเมื่อคุณทำงานกับอาร์เรย์สตริงหรือเมื่อคุณเรียกใช้ฟังก์ชันด้วยสำเนาของตัวแปร เป็นการยากที่จะเห็นรูปแบบของตรรกะภายในทั้งหมดนี้

เมื่อฉันควรใช้&และ*?


5
โปรดอธิบายว่าบางครั้งคุณเห็นสิ่งต่าง ๆ ทำงานแตกต่างกันอย่างไร ไม่เช่นนั้นเราต้องเดาว่าอะไรทำให้คุณสับสน
Drew Dormann

1
เห็นด้วยกับนีลบัตเตอร์เวิร์ ธ อาจเป็นไปได้ที่จะได้รับข้อมูลมากขึ้นจากการได้รับหนังสือจากมือแรกและคำอธิบาย K&R ค่อนข้างชัดเจน
Tom

145
ฉันไม่เห็นด้วยกับทุกคนที่พูดว่าไม่ใช่ความคิดที่ดีที่จะถามคำถามประเภทนี้ใน SO ดังนั้นได้กลายเป็นทรัพยากรหมายเลข 1 เมื่อค้นหาบน Google คุณไม่ได้ให้เครดิตเพียงพอสำหรับคำตอบเหล่านี้ อ่านคำตอบของ Dan Olson คำตอบนี้ลึกซึ้งและเป็นประโยชน์อย่างมากกับมือใหม่ RTFMไม่ช่วยเหลือและค่อนข้างหยาบคายมาก หากคุณไม่มีเวลาตอบก็ควรเคารพผู้ที่ใช้เวลาในการตอบคำถามเหล่านี้ ฉันหวังว่าฉันสามารถ @ นี้เพื่อ "อานนท์" แต่เห็นได้ชัดว่าเขา / เธอไม่มีเวลาที่จะมีส่วนร่วมในทางที่มีความหมายใด ๆ
SSH

18
สิ่งที่SSHพูดไว้นี้เป็นความจริงอย่างแน่นอน บางคนตะโกนว่า "แค่ Google นะ" แต่ทุกวันนี้มันก็เป็นอย่างอื่น: "แค่มองดู StackOverflow" คำถามนี้มีประโยชน์สำหรับคนจำนวนมาก (ดังนั้น upvotes และ downvote ไม่ได้)
MC Emperor

คำตอบ:


610

คุณมีพอยน์เตอร์และค่า:

int* p; // variable p is pointer to integer type
int i; // integer value

คุณเปลี่ยนตัวชี้เป็นค่าด้วย*:

int i2 = *p; // integer i2 is assigned with integer value that pointer p is pointing to

คุณเปลี่ยนค่าเป็นตัวชี้ด้วย&:

int* p2 = &i; // pointer p2 will point to the address of integer i

แก้ไข: ในกรณีของอาร์เรย์พวกเขาจะได้รับการปฏิบัติเหมือนตัวชี้ หากคุณคิดว่าพวกเขาเป็นตัวชี้คุณจะใช้*เพื่อรับค่าภายในของพวกเขาตามที่อธิบายไว้ข้างต้น แต่ยังมีอีกวิธีที่ใช้กันโดยทั่วไปมากขึ้นโดยใช้ตัว[]ดำเนินการ:

int a[2];  // array of integers
int i = *a; // the value of the first element of a
int i2 = a[0]; // another way to get the first element

ในการรับองค์ประกอบที่สอง:

int a[2]; // array
int i = *(a + 1); // the value of the second element
int i2 = a[1]; // the value of the second element

ดังนั้น[]โอเปอเรเตอร์การจัดทำดัชนีจึงเป็นรูปแบบพิเศษของ*โอเปอเรเตอร์และใช้งานเช่นนี้:

a[i] == *(a + i);  // these two statements are the same thing

2
ทำไมสิ่งนี้ถึงไม่ได้ผล? int aX[] = {3, 4}; int *bX = &aX;
ปีเตอร์

5
อาร์เรย์เป็นแบบพิเศษและสามารถแปลงเป็นพอยน์เตอร์ได้อย่างโปร่งใส นี่เป็นการเน้นวิธีอื่นในการรับจากตัวชี้ไปยังค่าฉันจะเพิ่มในคำอธิบายข้างต้น
Dan Olson

4
หากฉันเข้าใจอย่างถูกต้อง ... ตัวอย่างint *bX = &aX;นี้ใช้ไม่ได้เพราะที่aXอยู่ส่งคืนที่อยู่ของaX[0](เช่น&aX[0]) แล้วดังนั้น&aXจะได้รับที่อยู่ของที่อยู่ที่ไม่สมเหตุสมผล ถูกต้องหรือไม่
ปีเตอร์

6
คุณถูกต้องแม้ว่าจะมีหลายกรณีที่คุณอาจต้องการที่อยู่ของที่อยู่จริง ในกรณีนี้คุณจะประกาศว่า int ** bX = & aX แต่นี่เป็นหัวข้อขั้นสูง
Dan Olson

3
@Dan ให้int aX[] = {3,4};, int **bX = &aX;เป็นข้อผิดพลาด &aXเป็นประเภท "ตัวชี้ไปยังอาร์เรย์ [2] ของint" ไม่ใช่ "ตัวชี้ไปยังตัวชี้ไปยังint" &โดยเฉพาะชื่อของอาร์เรย์จะไม่ถือว่าเป็นตัวชี้ไปยังองค์ประกอบแรกสำหรับเอก คุณสามารถทำได้:int (*bX)[2] = &aX;
Alok Singhal

28

มีรูปแบบเมื่อจัดการกับอาร์เรย์และฟังก์ชั่น; มันเป็นเพียงเล็กน้อยยากที่จะเห็นในตอนแรก

เมื่อจัดการกับอาร์เรย์มันมีประโยชน์ที่จะจำสิ่งต่อไปนี้: เมื่อนิพจน์อาร์เรย์ปรากฏในบริบทส่วนใหญ่ประเภทของนิพจน์จะถูกแปลงโดยนัยจาก "อาร์เรย์องค์ประกอบ N ของ T" เป็น "ตัวชี้เป็น T" และตั้งค่าของมัน เพื่อชี้ไปที่องค์ประกอบแรกในอาร์เรย์ ข้อยกเว้นของกฎนี้คือเมื่อนิพจน์อาร์เรย์ปรากฏเป็นตัวถูกดำเนินการของตัวดำเนินการ&หรือsizeofหรือเมื่อเป็นตัวอักษรสตริงที่ใช้เป็นตัวเริ่มต้นในการประกาศ

ดังนั้นเมื่อคุณเรียกใช้ฟังก์ชันที่มีนิพจน์อาร์เรย์เป็นอาร์กิวเมนต์ฟังก์ชันจะรับตัวชี้ไม่ใช่อาร์เรย์:

int arr[10];
...
foo(arr);
...

void foo(int *arr) { ... }

นี่คือเหตุผลที่คุณไม่ใช้&โอเปอเรเตอร์เพื่อหาข้อโต้แย้งที่สอดคล้องกับ "% s" ในscanf():

char str[STRING_LENGTH];
...
scanf("%s", str);

เนื่องจากการแปลงโดยนัยscanf()ได้รับchar *ค่าที่ชี้ไปที่จุดเริ่มต้นของstrอาร์เรย์ นี่ถือเป็นจริงสำหรับฟังก์ชั่นใด ๆ ที่เรียกว่ามีการแสดงออกอาร์เรย์เป็นอาร์กิวเมนต์ (เพียงเกี่ยวกับstr*ฟังก์ชั่น*scanfและ*printfฟังก์ชั่นอื่น ๆ )

ในทางปฏิบัติคุณอาจไม่เคยเรียกใช้ฟังก์ชันที่มีนิพจน์อาร์เรย์โดยใช้&โอเปอเรเตอร์ดังเช่นใน:

int arr[N];
...
foo(&arr);

void foo(int (*p)[N]) {...}

รหัสดังกล่าวไม่ธรรมดามาก คุณต้องรู้ขนาดของอาเรย์ในการประกาศฟังก์ชั่นและฟังก์ชั่นจะทำงานร่วมกับพอยน์เตอร์ไปจนถึงอาร์เรย์ของขนาดเฉพาะ (ตัวชี้ไปยังอาเรย์ 10 องค์ประกอบของ T เป็นชนิดที่แตกต่างจากตัวชี้ไปยังอาเรย์ 11 องค์ประกอบ ของ T)

เมื่อนิพจน์อาเรย์ปรากฏเป็นตัวถูกดำเนินการกับโอเป&อเรเตอร์ชนิดของนิพจน์ที่เกิดขึ้นคือ "ตัวชี้ไปยังอาร์เรย์ขององค์ประกอบ N ของ T" หรือT (*)[N]ซึ่งแตกต่างจากอาเรย์ของพอยน์เตอร์ ( T *[N]) และตัวชี้ไปยังประเภทฐานT *)

เมื่อจัดการกับฟังก์ชั่นและพอยน์เตอร์กฎที่ต้องจำคือ: ถ้าคุณต้องการเปลี่ยนค่าของอาร์กิวเมนต์และให้มันสะท้อนในรหัสเรียกคุณต้องส่งตัวชี้ไปยังสิ่งที่คุณต้องการแก้ไข อีกครั้งอาร์เรย์โยนประแจลิงเข้าไปในงาน แต่เราจะจัดการกับกรณีปกติก่อน

จำไว้ว่า C ส่งค่าอาร์กิวเมนต์ของฟังก์ชันทั้งหมดตามค่า พารามิเตอร์ทางการได้รับสำเนาของค่าในพารามิเตอร์จริงและการเปลี่ยนแปลงใด ๆ กับพารามิเตอร์ที่เป็นทางการจะไม่ปรากฏในพารามิเตอร์จริง ตัวอย่างทั่วไปคือฟังก์ชันสลับ:

void swap(int x, int y) { int tmp = x; x = y; y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(a, b);
printf("after swap: a = %d, b = %d\n", a, b);

คุณจะได้ผลลัพธ์ต่อไปนี้:

ก่อนทำการแลกเปลี่ยน: a = 1, b = 2
หลังจากสลับ: a = 1, b = 2

พารามิเตอร์ที่เป็นทางการxและyเป็นวัตถุที่แตกต่างจากaและbเพื่อการเปลี่ยนแปลงxและyไม่ได้สะท้อนให้เห็นในและa bเนื่องจากเราต้องการแก้ไขค่าของaและbเราต้องส่งพอยน์เตอร์ไปยังฟังก์ชัน swap:

void swap(int *x, int *y) {int tmp = *x; *x = *y; *y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(&a, &b);
printf("after swap: a = %d, b = %d\n", a, b);

ตอนนี้ผลลัพธ์ของคุณจะเป็น

ก่อนทำการแลกเปลี่ยน: a = 1, b = 2
หลังจากสลับ: a = 2, b = 1

โปรดทราบว่าในฟังก์ชั่นการแลกเราไม่เปลี่ยนค่าของxและyแต่ค่าของสิ่งที่xและชี้ไปที่y เขียนถึง*xจะแตกต่างจากการเขียนx; เราไม่ได้อัปเดตค่าในxตัวเองเราได้รับตำแหน่งจากxและอัปเดตค่าในตำแหน่งนั้น

นี่เป็นเรื่องจริงถ้าเราต้องการแก้ไขค่าพอยน์เตอร์ ถ้าเราเขียน

int myFopen(FILE *stream) {stream = fopen("myfile.dat", "r"); }
...
FILE *in;
myFopen(in);

จากนั้นเราจะแก้ไขค่าของพารามิเตอร์อินพุตstreamไม่ใช่สิ่งที่stream ชี้ไปที่ดังนั้นการเปลี่ยนแปลงstreamจึงไม่มีผลกับค่าของin; เพื่อให้สิ่งนี้ใช้งานได้เราจะต้องผ่านตัวชี้ไปยังตัวชี้:

int myFopen(FILE **stream) {*stream = fopen("myFile.dat", "r"); }
...
FILE *in;
myFopen(&in);

อีกครั้งอาร์เรย์โยนประแจลิงลงไปในผลงาน เมื่อคุณส่งนิพจน์อาร์เรย์ไปยังฟังก์ชันสิ่งที่ฟังก์ชันรับคือตัวชี้ เนื่องจากวิธีการกำหนดตัวห้อยของอาร์เรย์คุณสามารถใช้ตัวดำเนินการตัวห้อยบนตัวชี้ในลักษณะเดียวกับที่คุณสามารถใช้ในอาร์เรย์:

int arr[N];
init(arr, N);
...
void init(int *arr, int N) {size_t i; for (i = 0; i < N; i++) arr[i] = i*i;}

โปรดทราบว่าวัตถุอาร์เรย์อาจไม่ได้รับมอบหมาย เช่นคุณไม่สามารถทำอะไรได้

int a[10], b[10];
...
a = b;

ดังนั้นคุณต้องระวังเมื่อคุณติดต่อกับพอยน์เตอร์ไปยังอาร์เรย์ สิ่งที่ต้องการ

void (int (*foo)[N])
{
  ...
  *foo = ...;
}

จะไม่ทำงาน


16

ใส่เพียง

  • &หมายถึงที่อยู่ของคุณจะเห็นว่าในตัวยึดตำแหน่งสำหรับฟังก์ชั่นในการปรับเปลี่ยนตัวแปรพารามิเตอร์เช่นเดียวกับใน C, ตัวแปรพารามิเตอร์จะถูกส่งผ่านตามค่าโดยใช้เครื่องหมายและหมายถึงการส่งผ่านโดยการอ้างอิง
  • *หมายความว่าdereferenceของตัวแปรชี้ความหมายที่จะได้รับค่าของตัวแปรชี้ว่า
int foo(int *x){
   *x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(&y);  // Now y is incremented and in scope here
   printf("value of y = %d\n", y); // output is 6
   /* ... */
}

ตัวอย่างข้างต้นแสดงวิธีเรียกใช้ฟังก์ชันfooโดยใช้การอ้างอิงแบบอ้างอิงเปรียบเทียบกับสิ่งนี้

int foo(int x){
   x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(y);  // Now y is still 5
   printf("value of y = %d\n", y); // output is 5
   /* ... */
}

นี่คือภาพประกอบของการใช้การอ้างถึง

int main(int argc, char **argv){
   int y = 5;
   int *p = NULL;
   p = &y;
   printf("value of *p = %d\n", *p); // output is 5
}

ดังกล่าวข้างต้นแสดงให้เห็นถึงวิธีการที่เรามีอยู่ของ และได้รับมอบหมายให้กับตัวแปรตัวชี้y pแล้วเราdereference pโดยติด*ไปยังด้านหน้าของมันที่จะได้รับค่าของคือp*p


10

ใช่ว่าค่อนข้างซับซ้อนเนื่องจาก*ใช้สำหรับวัตถุประสงค์ที่แตกต่างกันใน C / C ++

หาก*ปรากฏขึ้นข้างหน้าตัวแปร / ฟังก์ชั่นที่ประกาศไว้แล้วหมายความว่า:

  • ก) *ให้การเข้าถึงค่าของตัวแปรนั้น (หากประเภทของตัวแปรนั้นเป็นประเภทตัวชี้หรือ*ผู้ประกอบการทำงานมากเกินไป)
  • b) *มีความหมายของตัวดำเนินการคูณในกรณีนั้นจะต้องมีตัวแปรอีกตัวที่ด้านซ้ายของ*

หาก*ปรากฏในการประกาศตัวแปรหรือฟังก์ชั่นหมายความว่าตัวแปรนั้นเป็นตัวชี้:

int int_value = 1;
int * int_ptr; //can point to another int variable
int   int_array1[10]; //can contain up to 10 int values, basically int_array1 is an pointer as well which points to the first int of the array
//int   int_array2[]; //illegal, without initializer list..
int int_array3[] = {1,2,3,4,5};  // these two
int int_array4[5] = {1,2,3,4,5}; // are identical

void func_takes_int_ptr1(int *int_ptr){} // these two are identical
void func_takes int_ptr2(int int_ptr[]){}// and legal

หาก&ปรากฏในการประกาศตัวแปรหรือฟังก์ชั่นโดยทั่วไปหมายความว่าตัวแปรนั้นเป็นการอ้างอิงถึงตัวแปรประเภทนั้น

หาก&ปรากฏขึ้นด้านหน้าของตัวแปรที่ประกาศไว้แล้วมันจะส่งคืนที่อยู่ของตัวแปรนั้น

นอกจากนี้คุณควรรู้ว่าเมื่อผ่านอาร์เรย์ไปยังฟังก์ชั่นคุณจะต้องผ่านขนาดอาร์เรย์ของอาร์เรย์นั้นเช่นกันยกเว้นเมื่ออาร์เรย์เป็นสิ่งที่ต้องการ cstring สิ้นสุด 0 (อาร์เรย์อาร์เรย์)


1
@akmozo s / func_takes int_ptr2 / func_takes_int_ptr2 / (พื้นที่ไม่ถูกต้อง)
PixnBits

4

เมื่อคุณประกาศตัวแปรพอยน์เตอร์หรือฟังก์ชันพารามิเตอร์ให้ใช้ *:

int *x = NULL;
int *y = malloc(sizeof(int)), *z = NULL;
int* f(int *x) {
    ...
}

หมายเหตุ: ตัวแปรที่ประกาศแต่ละตัวต้องการ * ของมันเอง

เมื่อคุณต้องการจดที่อยู่ของค่าให้ใช้ &. เมื่อคุณต้องการอ่านหรือเขียนค่าในตัวชี้ให้ใช้ *

int a;
int *b;
b = f(&a);
a = *b;

a = *f(&a);

อาร์เรย์มักจะได้รับการปฏิบัติเหมือนตัวชี้ เมื่อคุณประกาศพารามิเตอร์อาเรย์ในฟังก์ชั่นคุณสามารถประกาศได้อย่างง่ายดายว่ามันคือตัวชี้ (มันหมายถึงสิ่งเดียวกัน) เมื่อคุณส่งอาร์เรย์ไปยังฟังก์ชั่นจริง ๆ แล้วคุณกำลังส่งตัวชี้ไปยังองค์ประกอบแรก

พอยน์เตอร์ของฟังก์ชั่นเป็นสิ่งเดียวที่ไม่ค่อยปฏิบัติตามกฎ คุณสามารถใช้ที่อยู่ของฟังก์ชั่นโดยไม่ใช้ & และคุณสามารถเรียกใช้ตัวชี้ฟังก์ชั่นโดยไม่ต้องใช้ *


4

ฉันถูกมองผ่านทุกคำอธิบายคำฟุ่มเฟือยดังนั้นแทนที่จะหันไปจนถึงวิดีโอจากมหาวิทยาลัยนิวเซาธ์เวลส์ rescue.Here เป็นคำอธิบายง่ายๆคือถ้าเรามีมือถือที่มีอยู่xและความคุ้มค่า7วิธีทางอ้อมเพื่อขอที่อยู่ของมูลค่า7เป็น&7และวิธีทางอ้อมเพื่อขอค่าที่อยู่xคือ*x. ดังนั้น(cell: x , value: 7) == (cell: &7 , value: *x)วิธีอื่นในการดู: Johnนั่งที่7th seat. *7th seatจะชี้ไปที่Johnและ&Johnจะให้address/ ที่ตั้งของ7th seat. คำอธิบายง่ายๆนี้ช่วยฉันและหวังว่าจะช่วยคนอื่นได้เช่นกัน นี่คือลิงค์สำหรับวิดีโอที่ยอดเยี่ยม: คลิกที่นี่

นี่เป็นอีกตัวอย่าง:

#include <stdio.h>

int main()
{ 
    int x;            /* A normal integer*/
    int *p;           /* A pointer to an integer ("*p" is an integer, so p
                       must be a pointer to an integer) */

    p = &x;           /* Read it, "assign the address of x to p" */
    scanf( "%d", &x );          /* Put a value in x, we could also use p here */
    printf( "%d\n", *p ); /* Note the use of the * to get the value */
    getchar();
}

Add-on: เริ่มต้นตัวชี้ทุกครั้งก่อนที่จะใช้ตัวชี้ถ้าไม่ตัวชี้จะชี้ไปที่สิ่งใดซึ่งอาจส่งผลให้โปรแกรมทำงานล้มเหลวเนื่องจากระบบปฏิบัติการจะป้องกันไม่ให้คุณเข้าถึงหน่วยความจำที่รู้ว่าคุณไม่ได้เป็นเจ้าของ p = &x;เรากำลังกำหนดตำแหน่งเฉพาะของตัวชี้


3

ที่จริงแล้วคุณมีปัญหากับมันไม่มีอะไรที่คุณต้องรู้อีก :-)

ฉันจะเพิ่มบิตต่อไปนี้:

  • การดำเนินการทั้งสองอยู่ตรงข้ามปลายของสเปกตรัม &ใช้ตัวแปรและให้ที่อยู่แก่คุณรับที่*อยู่และให้ตัวแปร (หรือเนื้อหา) ให้คุณ
  • อาร์เรย์ "ลดระดับ" ถึงตัวชี้เมื่อคุณส่งไปยังฟังก์ชัน
  • คุณสามารถจริงมีหลายระดับในความร้าย ( char **pหมายความว่าเป็นตัวชี้ไปชี้ไปยังที่pchar

สำหรับสิ่งที่ทำงานแตกต่างกันไม่จริง:

  • อาร์เรย์ดังที่กล่าวไปแล้วลดระดับลงสู่พอยน์เตอร์ (ไปยังองค์ประกอบแรกในอาร์เรย์) เมื่อส่งผ่านไปยังฟังก์ชัน พวกเขาไม่เก็บข้อมูลขนาด
  • ไม่มีสตริงใน C เพียงแค่อาร์เรย์อักขระที่ตามแบบแผนจะแสดงสตริงของอักขระที่สิ้นสุดด้วยอักขระศูนย์ ( \0)
  • เมื่อคุณส่งที่อยู่ของตัวแปรไปยังฟังก์ชั่นคุณสามารถยกเลิกการอ้างอิงตัวชี้เพื่อเปลี่ยนตัวแปรได้ (โดยปกติแล้วตัวแปรจะถูกส่งผ่านตามค่า (ยกเว้นสำหรับอาร์เรย์))

3

ฉันคิดว่าคุณค่อนข้างสับสน คุณควรอ่านบทแนะนำ / หนังสือเกี่ยวกับพอยน์เตอร์ที่ดี

นี้กวดวิชาเป็นอย่างดีสำหรับการเริ่ม (อธิบายอย่างชัดเจนสิ่งที่&และ*มี) และอย่าลืมอ่านหนังสือพอยน์เตอร์ใน Cโดย Kenneth Reek

ความแตกต่างระหว่าง&และ*ชัดเจนมาก

ตัวอย่าง:

#include <stdio.h>

int main(){
  int x, *p;

  p = &x;         /* initialise pointer(take the address of x) */
  *p = 0;         /* set x to zero */
  printf("x is %d\n", x);
  printf("*p is %d\n", *p);

  *p += 1;        /* increment what p points to i.e x */
  printf("x is %d\n", x);

  (*p)++;         /* increment what p points to i.e x */
  printf("x is %d\n", x);

  return 0;
}

1

ตกลงดูเหมือนว่าโพสต์ของคุณจะถูกแก้ไข ...

double foo[4];
double *bar_1 = &foo[0];

มาดูกันว่าคุณสามารถใช้&เพื่อรับที่อยู่ของจุดเริ่มต้นของโครงสร้างอาร์เรย์ได้อย่างไร ดังต่อไปนี้

Foo_1(double *bar, int size){ return bar[size-1]; }
Foo_2(double bar[], int size){ return bar[size-1]; }

จะทำในสิ่งเดียวกัน


คำถามถูกติดแท็ก C ไม่ใช่ C ++
Prasoon Saurav

1
และฉันได้ลบการแข่งขันที่ละเมิด <<
วีตที่
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.