If by “harder” you mean “more effort required to fully understand what’s going on”, then you’re right.
If by “harder” you mean “more effort required to use it” then you’re wrong.
I’m going to compare Assembly, C and Python.
We’re going to look at the helloworld programs of those three languages. It’s purpose is to output Hello World!
and exit.
Python 3:
Create a new file, let’s call it helloworld-python.py
and write to it this:
print("Hello World!")
Here we see the execution of the program and the output ($ is the shell input):
$ python3 helloworld-python.py
Hello World!
This required astonishing little work! Let’s now look at C.
C:
Create a new file, let’s call it helloworld-c.c
and write to it this:
#include <stdio.h>
int main(void) {
printf("Hello World!\n");
return 0;
}
Here’s the compilation, execution and output:
$ gcc -Wall -o helloworld-c helloworld-c.c
$ ./helloworld-c
Hello World!
(-Wall
tells gcc to output all warnings and -o
how the output file should be called)
You can see that it is a bit longer. This program may not be so obvious, so I’m going to explain it.
#include <stdio.h>
This includes a header file which provides you a lot of input and output related stuff(including the printf
function we used in this program.
int main(void) {
This part creates a function whose return value is an integer(on x86 processors an integer is 4 bytes big), is called main(how we refer to the function) and takes no parameters.
printf("Hello World!\n");
This calls the printf
function from stdio.h
and passes as argument a pointer to an char(a char is 1 byte big on x86 processors) array(often also called “string”).
return 0;
This says that the the return value of the function is 0. It’s also the exit status code of the program(you can execute echo $?
to see it).
Let’s now look at assembly.
Assembly(without libraries):
Now, with Assembly, your first program is, generally, one that only returns a exit status code and exits. But here’s the helloworld-asm.s
file:
.code32
.section .data
hello_world:
.ascii "Hello World!\n"
hello_world_end:
.equ hello_world_len, hello_world_end - hello_world
.section .text
.globl _start
_start:
#This part deals with outputting what's located at hello_world.
movl $4, %eax
movl $1, %ebx
movl $hello_world, %ecx
movl $hello_world_len, %edx
int $0x80
#This part deals with exiting.
movl $1, %eax
movl $0, %ebx
int $0x80
To assemble it, link it and run it do this:
$ as --32 -o helloworld-asm.o helloworld-asm.s
$ ld -m elf_i386 -o helloworld-asm helloworld-asm.o
$ ./helloworld-asm
Hello World!
(I’m assuming you’re using a x86-64 machine. That’s why I have use the --32
and -m elf_i386
flags.)
You could also have used gcc for this.
I’m already tired so I’m going to jump right into the for you interesting stuff.
movl $4, %eax
This moves 4 bytes(the suffix(in this case l
) defines how much is copied) with the value 4
to the eax
register. 4 is the linux system call for write.
movl $1, %ebx
The write system call needs the file descriptor in ebx
, so we move 1(it’s the file descriptor for stdout
) to it.
movl $hello_world, %ecx
This moves the pointer to our string to ecx
. The write sys call needs the pointer to the data to write in ecx
.
movl $hello_world_len, %edx
The write system call need to know the amount of data from the memory location refered to in ecx
to write in edx
. So we move the length of our string into it. You can try to substitute $hello_world_len
with any number you like and have some fun(don’t forget to put the “$” behind the number or the assembler will thinks it’s an memory location and when you run it, you’ll probably get a segfault).
int $0x80
This calls Linux. What you want to do is written in the eax
register(write to a file).
movl $1, %eax
This moves 1 to eax
. 1 is the exit system call.
movl $0, %ebx
This moves 0 to ebx
. This is the exit status code.
(I’m probably someday going to expand the assembly section.)
You’ve now seen some examples. Now I’d like to explain what I meant with the first two paragraphs.
Python makes your life easier. You don’t need to think about memory management, pointers and interrupts. Yet, due to the higher abstraction, you’ve less control over what happens and you can only know what’s happening in the background by reading the interpreter’s source code. To fully understand what happens on your machine, you need to go deeper.
With Assembly you get a lot of control in your hands and can learn how the machine works, but it’s really frustrating to use it(learning to use a debugger is recommended). You have to think about many more details than any other language.
I have a crappy analogy for you:
As an Assembly programmer, you’re an absolutist Emperor of an Empire. Every part of it is in your control. Every corner of it is in your control. You may enjoy your power but leading a nation is hard, even more if it’s one of that size. The power may be too much than you can handle and the Empire will probably fall because of that. When you’re in need money you do something like this:
“Ok, I’m going to raise the taxes by 15% for these people here and apply these laws.”
As a Ruby programmer, you’re the leader of a nation of the same size. You still have a central role but you have many ministers and governors to help you. When you need money, you do something like this:
“Hey, Jeff, I need some money. Do something about that.”
I recommend to learn a high level language first and then lower-level stuff because it’s easier to stay motivated if the cool stuff already happens soon and the despair not too early.