473,770 Members | 1,629 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

about the keyword "register"

howdy....
plz take a look at the following codes, and tell me the reason.

1 #define swap(a,b) a=a^b;b=b^a;a=a ^b
2
3 int main(void){
4 register int a=4;
5 register int b=5;
6 swap(a,b);
7
8 return 0;
9 }
The assemble code of the above code.

(gdb) disassemble main
Dump of assembler code for function main:
0x08048334 <main+0>: push %ebp
0x08048335 <main+1>: mov %esp,%ebp
0x08048337 <main+3>: sub $0x8,%esp
0x0804833a <main+6>: and $0xfffffff0,%es p
0x0804833d <main+9>: mov $0x0,%eax
0x08048342 <main+14>: add $0xf,%eax
0x08048345 <main+17>: add $0xf,%eax
0x08048348 <main+20>: shr $0x4,%eax
0x0804834b <main+23>: shl $0x4,%eax
0x0804834e <main+26>: sub %eax,%esp
0x08048350 <main+28>: mov $0x0,%eax
0x08048355 <main+33>: leave
0x08048356 <main+34>: ret
End of assembler dump.

where is the XOR instruction?

if I remove the keyword "register" in my C program.
1 #define swap(a,b) a=a^b;b=b^a;a=a ^b
2
3 int main(void){
4 int a=4;
5 int b=5;
6 swap(a,b);
7 return 0;
8 }

I'll got the following "monster" asm code...
(gdb) disassemble main
Dump of assembler code for function main:
0x08048334 <main+0>: push %ebp
0x08048335 <main+1>: mov %esp,%ebp
0x08048337 <main+3>: sub $0x8,%esp
0x0804833a <main+6>: and $0xfffffff0,%es p
0x0804833d <main+9>: mov $0x0,%eax
0x08048342 <main+14>: add $0xf,%eax
0x08048345 <main+17>: add $0xf,%eax
0x08048348 <main+20>: shr $0x4,%eax
0x0804834b <main+23>: shl $0x4,%eax
0x0804834e <main+26>: sub %eax,%esp
0x08048350 <main+28>: movl $0x4,0xfffffffc (%ebp)
0x08048357 <main+35>: movl $0x5,0xfffffff8 (%ebp)
0x0804835e <main+42>: mov 0xfffffff8(%ebp ),%edx
0x08048361 <main+45>: lea 0xfffffffc(%ebp ),%eax
0x08048364 <main+48>: xor %edx,(%eax)
0x08048366 <main+50>: mov 0xfffffffc(%ebp ),%edx
0x08048369 <main+53>: lea 0xfffffff8(%ebp ),%eax
0x0804836c <main+56>: xor %edx,(%eax)
0x0804836e <main+58>: mov 0xfffffff8(%ebp ),%edx
0x08048371 <main+61>: lea 0xfffffffc(%ebp ),%eax
0x08048374 <main+64>: xor %edx,(%eax)
0x08048376 <main+66>: mov $0x0,%eax
0x0804837b <main+71>: leave
0x0804837c <main+72>: ret
End of assembler dump.

Another strange thing is the xor instruction.
Plz see the code:
0x0804835e <main+42>: mov 0xfffffff8(%ebp ),%edx
0x08048361 <main+45>: lea 0xfffffffc(%ebp ),%eax
0x08048364 <main+48>: xor %edx,(%eax)
0x08048366 <main+50>: mov 0xfffffffc(%ebp ),%edx
0x08048369 <main+53>: lea 0xfffffff8(%ebp ),%eax
0x0804836c <main+56>: xor %edx,(%eax)

why dont use
xor %edx,(%eax)
xor %eax,(%edx)
to replace the redundent
0x08048366 <main+50>: mov 0xfffffffc(%ebp ),%edx
0x08048369 <main+53>: lea 0xfffffff8(%ebp ),%eax

Thanx!!!!
Nov 14 '05 #1
13 2095

Dave win wrote:
howdy....
plz take a look at the following codes, and tell me the reason.

1 #define swap(a,b) a=a^b;b=b^a;a=a ^b
This macro will break when used in a loop.
Use
#define swap(a,b) do { \
a=a^b;b=b^a;a=a ^b;\
} while(0)
to circumvent this problem.
Note that this XOR trick is not necessarily faster
than whatever your compiler does when you let it
optimise the code.
2
3 int main(void){
4 register int a=4;
5 register int b=5;
6 swap(a,b);
7
8 return 0;
9 }
The assemble code of the above code.

(gdb) disassemble main
Dump of assembler code for function main:
0x08048334 <main+0>: push %ebp
0x08048335 <main+1>: mov %esp,%ebp
0x08048337 <main+3>: sub $0x8,%esp
0x0804833a <main+6>: and $0xfffffff0,%es p
0x0804833d <main+9>: mov $0x0,%eax
0x08048342 <main+14>: add $0xf,%eax
0x08048345 <main+17>: add $0xf,%eax
0x08048348 <main+20>: shr $0x4,%eax
0x0804834b <main+23>: shl $0x4,%eax
0x0804834e <main+26>: sub %eax,%esp
0x08048350 <main+28>: mov $0x0,%eax
0x08048355 <main+33>: leave
0x08048356 <main+34>: ret
End of assembler dump.

where is the XOR instruction?

if I remove the keyword "register" in my C program.
1 #define swap(a,b) a=a^b;b=b^a;a=a ^b
2
3 int main(void){
4 int a=4;
5 int b=5;
6 swap(a,b);
7 return 0;
8 }

I'll got the following "monster" asm code...
(gdb) disassemble main
Dump of assembler code for function main:
0x08048334 <main+0>: push %ebp
0x08048335 <main+1>: mov %esp,%ebp
0x08048337 <main+3>: sub $0x8,%esp
0x0804833a <main+6>: and $0xfffffff0,%es p
0x0804833d <main+9>: mov $0x0,%eax
0x08048342 <main+14>: add $0xf,%eax
0x08048345 <main+17>: add $0xf,%eax
0x08048348 <main+20>: shr $0x4,%eax
0x0804834b <main+23>: shl $0x4,%eax
0x0804834e <main+26>: sub %eax,%esp
0x08048350 <main+28>: movl $0x4,0xfffffffc (%ebp)
0x08048357 <main+35>: movl $0x5,0xfffffff8 (%ebp)
0x0804835e <main+42>: mov 0xfffffff8(%ebp ),%edx
0x08048361 <main+45>: lea 0xfffffffc(%ebp ),%eax
0x08048364 <main+48>: xor %edx,(%eax)
0x08048366 <main+50>: mov 0xfffffffc(%ebp ),%edx
0x08048369 <main+53>: lea 0xfffffff8(%ebp ),%eax
0x0804836c <main+56>: xor %edx,(%eax)
0x0804836e <main+58>: mov 0xfffffff8(%ebp ),%edx
0x08048371 <main+61>: lea 0xfffffffc(%ebp ),%eax
0x08048374 <main+64>: xor %edx,(%eax)
0x08048376 <main+66>: mov $0x0,%eax
0x0804837b <main+71>: leave
0x0804837c <main+72>: ret
End of assembler dump.

Another strange thing is the xor instruction.
Plz see the code:
0x0804835e <main+42>: mov 0xfffffff8(%ebp ),%edx
0x08048361 <main+45>: lea 0xfffffffc(%ebp ),%eax
0x08048364 <main+48>: xor %edx,(%eax)
0x08048366 <main+50>: mov 0xfffffffc(%ebp ),%edx
0x08048369 <main+53>: lea 0xfffffff8(%ebp ),%eax
0x0804836c <main+56>: xor %edx,(%eax)

why dont use
xor %edx,(%eax)
xor %eax,(%edx)
to replace the redundent
0x08048366 <main+50>: mov 0xfffffffc(%ebp ),%edx
0x08048369 <main+53>: lea 0xfffffff8(%ebp ),%eax


This is not topical in comp.lang.c; please ask in a newsgroup
dedicated to your compiler and/or platform. I suggest
gnu.gcc.help for a start.
Cheers
Michael
--
E-Mail: Mine is a gmx dot de address.

Nov 14 '05 #2
On Tue, 28 Dec 2004 17:17:44 +0800, Dave win <wc***@cs.ccu.e du.tw>
wrote in comp.lang.c:
howdy....
plz take a look at the following codes, and tell me the reason.

1 #define swap(a,b) a=a^b;b=b^a;a=a ^b
2
3 int main(void){
4 register int a=4;
5 register int b=5;
6 swap(a,b);
7
8 return 0;
9 }
The assemble code of the above code.
[snip off-topic assembly language]
where is the XOR instruction?


[snip]

The compiler can verify that the values of 'a' and 'b' are never used.
By the as-if rule, it can eliminate all of your main() function other
than the return statement.

--
Jack Klein
Home: http://JK-Technology.Com
FAQs for
comp.lang.c http://www.eskimo.com/~scs/C-faq/top.html
comp.lang.c++ http://www.parashift.com/c++-faq-lite/
alt.comp.lang.l earn.c-c++
http://www.contrib.andrew.cmu.edu/~a...FAQ-acllc.html
Nov 14 '05 #3

"Dave win" <wc***@cs.ccu.e du.tw> wrote
howdy....
plz take a look at the following codes, and tell me the reason.

1 #define swap(a,b) a=a^b;b=b^a;a=a ^b
2
3 int main(void){
4 register int a=4;
5 register int b=5;
6 swap(a,b);
7
8 return 0;
9 }
The assemble code of the above code.

(gdb) disassemble main
Dump of assembler code for function main:
0x08048334 <main+0>: push %ebp
0x08048335 <main+1>: mov %esp,%ebp
0x08048337 <main+3>: sub $0x8,%esp
0x0804833a <main+6>: and $0xfffffff0,%es p
0x0804833d <main+9>: mov $0x0,%eax
0x08048342 <main+14>: add $0xf,%eax
0x08048345 <main+17>: add $0xf,%eax
0x08048348 <main+20>: shr $0x4,%eax
0x0804834b <main+23>: shl $0x4,%eax
0x0804834e <main+26>: sub %eax,%esp
0x08048350 <main+28>: mov $0x0,%eax
0x08048355 <main+33>: leave
0x08048356 <main+34>: ret
End of assembler dump.

Your problem is that you have given a trivial function to the compiler to
optimise. Since you have put a and b in registers, it has decided to
optimise very aggressively, which in this case means just calling main and
returning. The assembly instructions left probably just set up the stack
frame.

If you are trying to write little functions to see what the compiler does,
which is a totally legitimate and sensible thing to do though not strictly
on-topic, the best thing to do is to make a real function, for instance by
reading two integers from stdin and swapping them, then outputting the
result.

Nov 14 '05 #4
A couple of things:

1. Either enclose the macro definition in parenthesis [i.e. -- ()'s]
with comma seperation instead of semicolon seperation, or enclose the
macro definition in the do{}while(0) construct suggested by another
poster. Personally, I use parenthesis when I can so that the macro can
lexically be used just like a function, but either approach will avoid
non-obvious problems.

2. If it is your objective to force the compiler to do things your way
(i.e. swap using exclusive or) without incurring all of the bulk due to
automatic variables, then try using the keyword 'volatile' instead of
'register' and compiling the program with maximum optimization. In
general there is no guarantee that a compiler will produce the results
you desire however and no guarantee that it won't do a better job than
what you desire either.

Nov 14 '05 #5
Dave win wrote:
howdy....
plz take a look at the following codes, and tell me the reason.

1 #define swap(a,b) a=a^b;b=b^a;a=a ^b
2
3 int main(void){
4 register int a=4;
5 register int b=5;
6 swap(a,b);
7
8 return 0;
9 }
The assemble code of the above code.

(gdb) disassemble main
Dump of assembler code for function main:
0x08048334 <main+0>: push %ebp
0x08048335 <main+1>: mov %esp,%ebp
0x08048337 <main+3>: sub $0x8,%esp
0x0804833a <main+6>: and $0xfffffff0,%es p
0x0804833d <main+9>: mov $0x0,%eax
0x08048342 <main+14>: add $0xf,%eax
0x08048345 <main+17>: add $0xf,%eax
0x08048348 <main+20>: shr $0x4,%eax
0x0804834b <main+23>: shl $0x4,%eax
0x0804834e <main+26>: sub %eax,%esp
0x08048350 <main+28>: mov $0x0,%eax
0x08048355 <main+33>: leave
0x08048356 <main+34>: ret
End of assembler dump.

where is the XOR instruction?
...


What 'XOR instruction'? Are you implying that there supposed to be some
kind of definitive correspondence between C language operators and
machine commands, like '^' is supposed to translate into a 'xor'
instruction? That would be nonsense. There is absolutely no definitive
matching between the two. There's absolutely no reason to expect to see
a 'xor' instruction in machine code that evaluates a C expression with
'^' operator. In this case the compiler decided to evaluate your
expressions without using 'xor' instruction. There's nothing wrong
and/or unusual with this as long as the final result is correct.

--
Best regards,
Andrey Tarasevich
Nov 14 '05 #6

"Andrey Tarasevich" <an************ **@hotmail.com> wrote
There's absolutely no reason to expect to see
a 'xor' instruction in machine code that evaluates a C expression with
'^' operator.

That's way too strong. The reason C has a xor operator, but not an
exponentiation operator for example, is that xor is typically implemented as
a machine instruction whilst pow() is not. So if I compiled source using the
xor operator, and found that the resulting assembly does not contain a xor
instruction, I would indeed be surprised. In this case the reason is that
the function can be aggressively optimised to nothing, because there is no
output. That is arguably not desirable behaviour from a compiler, though the
standard does allow it. And of course xor might be implemented with a
combination of ands and ors if some quirk of the platform made that more
efficient, but one would not expect that situation to arise normally.
Nov 14 '05 #7
Malcolm wrote:
"Andrey Tarasevich" <an************ **@hotmail.com> wrote
There's absolutely no reason to expect to see
a 'xor' instruction in machine code that evaluates a C expression with
'^' operator.

That's way too strong. The reason C has a xor operator, but not an
exponentiation operator for example, is that xor is typically implemented as
a machine instruction whilst pow() is not. So if I compiled source using the
xor operator, and found that the resulting assembly does not contain a xor
instruction, I would indeed be surprised. In this case the reason is that
the function can be aggressively optimised to nothing, because there is no
output. That is arguably not desirable behaviour from a compiler, though the
standard does allow it. And of course xor might be implemented with a
combination of ands and ors if some quirk of the platform made that more
efficient, but one would not expect that situation to arise normally.


This not too strong in general case. Not at all. An optimizing compiler
could be smart enough to realize that certain code does not really need
an actual 'xor' and can be implemented more efficiently without it.

For example, the same reasoning that you use above can be used to
conclude that both multiplication and shift operators were also added to
C language because they are typically matched by corresponding machine
instructions. However, on many platforms the most efficient way to
implement multiplication to a constant power-of-2 coefficient could be
neither machine 'mul' operation nor machine shift operation. It could be
some seemingly unrelated load-effective-address operation ('lea' of x86,
for example) or something completely different. I won't be surprised to
see that both expression 'foo *= 4' and expression 'bar <<= 2' got
translated into identical something like

lea eax, [eax*4]

instead of "expected" applications of 'mul's and/or shifts.

Something like this can easily happen to '^' as well. The compiler might
decide that is some particular context a '^' operation can be safely and
efficiently replaced with a non-equality check, for example.

Assumption that there's some kind of 1:1 correspondence between C
operations and machine instructions is not a good thing. It often
results in unreadable code (because of heavy use of useless
optimizations, like replacing multiplications with shifts). It often
encourages users to make further ungrounded assumptions about the order
of evaluation of C expressions, resulting in broken code.

--
Best regards,
Andrey Tarasevich
Nov 14 '05 #8
Andrey Tarasevich <an************ **@hotmail.com> writes:
Malcolm wrote:
"Andrey Tarasevich" <an************ **@hotmail.com> wrote
There's absolutely no reason to expect to see
a 'xor' instruction in machine code that evaluates a C expression with
'^' operator.
That's way too strong. The reason C has a xor operator, but not an
exponentiation operator for example, is that xor is typically implemented as
a machine instruction whilst pow() is not. So if I compiled source using the
xor operator, and found that the resulting assembly does not contain a xor
instruction, I would indeed be surprised. In this case the reason is that
the function can be aggressively optimised to nothing, because there is no
output. That is arguably not desirable behaviour from a compiler, though the
standard does allow it. And of course xor might be implemented with a
combination of ands and ors if some quirk of the platform made that more
efficient, but one would not expect that situation to arise normally.


This not too strong in general case. Not at all. An optimizing compiler
could be smart enough to realize that certain code does not really need
an actual 'xor' and can be implemented more efficiently without it.

For example, the same reasoning that you use above can be used to
conclude that both multiplication and shift operators were also added to
C language because they are typically matched by corresponding machine
instructions. However, on many platforms the most efficient way to
implement multiplication to a constant power-of-2 coefficient could be
neither machine 'mul' operation nor machine shift operation. It could be
some seemingly unrelated load-effective-address operation ('lea' of x86,
for example) or something completely different. I won't be surprised to
see that both expression 'foo *= 4' and expression 'bar <<= 2' got
translated into identical something like

lea eax, [eax*4]

instead of "expected" applications of 'mul's and/or shifts.


First, any operation can be eliminated if its result isn't used, which
I think is what was happening with the OP's code.

Leaving that aside, there are a lot of cases where a multiplication by
a constant can be replaced by some cheaper operation such as a shift
or an addition (assuming the compiler knows, or can guess, how
expensive the operations are). But a multiplication of two variables
(assuming the compiler can't derive their actual values) will probably
show up as a multiplication operator in the generated code.
Something like this can easily happen to '^' as well. The compiler might
decide that is some particular context a '^' operation can be safely and
efficiently replaced with a non-equality check, for example.


That can happen if the compiler knows that both operands have the
value 0 or 1 (but only if a non-equality check is cheaper than an xor
instruction, which I expect it normally wouldn't be). Similarly, an
xor operation might be eliminated if one of the operands is a
constant, or can be evaluated at compilation time (but even then an
xor instruction might still be the cheapest way to do the
calculation), or if the result isn't used. But if the operands are
variables whose values can't be computed at compilation time and the
result is used, a "^" operation will probably appear as an xor
instruction in the generated code. If an xor instruction isn't the
cheapest way to perform an exclusive-or, there's no point in having it
in the instruction set.

Here's a simple example:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
int x, y;
if (argc != 3) {
exit(EXIT_FAILU RE);
}
x = atoi(argv[1]);
y = atoi(argv[2]);
printf("%d & %d = %d\n", x, y, x * y);
printf("%d ^ %d = %d\n", x, y, x ^ y);
return 0;
}

One interesting question is how many "*" and "^" operators in real
code are best implemented by actual multiply and xor instructions, and
how many can be reduced to something cheaper. My guess is that
multiplication can be reduced more often that xor, but beyond that I
don't know what the proportions are.

--
Keith Thompson (The_Other_Keit h) ks***@mib.org <http://www.ghoti.net/~kst>
San Diego Supercomputer Center <*> <http://users.sdsc.edu/~kst>
We must do something. This is something. Therefore, we must do this.
Nov 14 '05 #9
On Wed, 29 Dec 2004 09:28:02 +0000, Keith Thompson wrote:

....
One interesting question is how many "*" and "^" operators in real
code are best implemented by actual multiply and xor instructions, and
how many can be reduced to something cheaper. My guess is that
multiplication can be reduced more often that xor, but beyond that I
don't know what the proportions are.


Consider this:

#include <stdio.h>

#define swap(a,b) ((a)=(a)^(b),(b )=(b)^(a),(a)=( a)^(b))

void foo(int x, int y)
{
printf("%d %d\n", x, y);
swap(x, y);
printf("%d %d\n", x, y);
}

Which gcc was able to compile into:

foo:
subl $28, %esp
movl %ebx, 20(%esp)
movl 36(%esp), %ebx
movl %esi, 24(%esp)
movl 32(%esp), %esi
movl %ebx, 8(%esp)
movl $.LC0, (%esp)
movl %esi, 4(%esp)
xchgl %ebx, %esi
call printf
movl %ebx, 8(%esp)
movl %esi, 4(%esp)
movl $.LC0, (%esp)
call printf
movl 20(%esp), %ebx
movl 24(%esp), %esi
addl $28, %esp
ret

Note here that gcc was clever enough to recognise that IN THIS CASE the
sequence of ^ operations amount to a swap operation so didn't have to
generate any xor instructions. If it had been a little bit cleverer it
could have eliminated the xchgl instruction too. Writing it as:

#include <stdio.h>

#define swap(a,b) do { int tmp_ = (a); (a) = (b); (b) = tmp_; } while (0)

void bar(int x, int y)
{
printf("%d %d\n", x, y);
swap(x, y);
printf("%d %d\n", x, y);
}

produced:

bar:
subl $28, %esp
movl %ebx, 20(%esp)
movl 32(%esp), %ebx
movl %esi, 24(%esp)
movl 36(%esp), %esi
movl %ebx, 4(%esp)
movl $.LC0, (%esp)
movl %esi, 8(%esp)
call printf
movl %ebx, 8(%esp)
movl %esi, 4(%esp)
movl $.LC0, (%esp)
call printf
movl 20(%esp), %ebx
movl 24(%esp), %esi
addl $28, %esp
ret

I.e. writing the simple, obvious swap code rather than something "clever"
results here in more efficient code (it managed to eliminate the xchgl
instruction). Note that there is no code here that corresponds to the
assignments in the swap, it just picks the values from the right places.

Lawrence
Nov 14 '05 #10

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

65
2613
by: Spiros Bousbouras | last post by:
Has anyone found that declaring variables register affected speed of execution ? If yes on what hardware and around which year ?
2
1731
by: sheldonlg | last post by:
I did some coding on a site where register_globals is set to on. The problem I encountered was that the session variable changed without my changing it explicitly. I knew that in register globals being on, that all the variables were global variables. What I didn't realize was that it set up an equivalence such that the variable is an alias for the session variable with the key name of that variable. That is, $_SESSION is the same as...
0
9595
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However, people are often confused as to whether an ONU can Work As a Router. In this blog post, we’ll explore What is ONU, What Is Router, ONU & Router’s main usage, and What is the difference between ONU and Router. Let’s take a closer look ! Part I. Meaning of...
0
9432
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can effortlessly switch the default language on Windows 10 without reinstalling. I'll walk you through it. First, let's disable language synchronization. With a Microsoft account, language settings sync across devices. To prevent any complications,...
1
10008
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows Update option using the Control Panel or Settings app; it automatically checks for updates and installs any it finds, whether you like it or not. For most users, this new feature is actually very convenient. If you want to control the update process,...
0
9873
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each protocol has its own unique characteristics and advantages, but as a user who is planning to build a smart home system, I am a bit confused by the choice of these technologies. I'm particularly interested in Zigbee because I've heard it does some...
1
7420
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new presenter, Adolph Dupré who will be discussing some powerful techniques for using class modules. He will explain when you may want to use classes instead of User Defined Types (UDT). For example, to manage the data in unbound forms. Adolph will...
0
5313
by: TSSRALBI | last post by:
Hello I'm a network technician in training and I need your help. I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs. The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols. I succeeded, with both firewalls in the same network. But I'm wondering if it's possible to do the same thing, with 2 Pfsense firewalls...
1
3974
by: 6302768590 | last post by:
Hai team i want code for transfer the data from one system to another through IP address by using C# our system has to for every 5mins then we have to update the data what the data is updated we have to send another system
2
3578
muto222
by: muto222 | last post by:
How can i add a mobile payment intergratation into php mysql website.
3
2822
bsmnconsultancy
by: bsmnconsultancy | last post by:
In today's digital era, a well-designed website is crucial for businesses looking to succeed. Whether you're a small business owner or a large corporation in Toronto, having a strong online presence can significantly impact your brand's success. BSMN Consultancy, a leader in Website Development in Toronto offers valuable insights into creating effective websites that not only look great but also perform exceptionally well. In this comprehensive...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.